Policy Based Authorization In ASP.Net Core Web API Using Open Policy Agent

Amit Anand
5 min readFeb 20, 2023

--

Securing sensitive or valuable data is crucial for any application, and authorization plays a pivotal role in achieving that. For a Web API, ensuring that only authorized users access the protected resources is very important. Open Policy Agent (OPA) is an excellent solution that empowers you to create and enforce authorization policies within your API. Open Policy Agent (OPA) employs Rego, a declarative language, to define policies and also provides a highly adaptable policy evaluation engine.

This article aims to guide you through the process of seamlessly integrating Open Policy Agent (OPA) with an ASP.NET Core Web API. We’ll provide step-by-step instructions on defining sample policies in Rego language and creating a custom middleware to evaluate them. Lastly, we’ll demonstrate how to integrate the middleware with the Web API to ensure that incoming requests comply with the defined policies.

Prerequisites

In order to integrate Open Policy Agent (OPA) with ASP.Net Core Web API, we will need the following:

  • A basic understanding of C# and ASP.NET Core.
  • ASP.NET Core SDK 2.1 or later.
  • Visual Studio or any another IDE of our choice that supports .Net Core development.
  • A running instance of Open Policy Agent (OPA). We will assume it’s running on http://localhost:9191.

Install and Configure the OPA Server

Before we can start writing the implementation to integrate Open Policy Agent with the ASP.NET Core Web API, we need to install and configure the Open Policy Agent (OPA) server. Here are the steps to do that:

  1. Download the latest Open Policy Agent (OPA) server binary for your platform from the OPA releases page.
  2. Extract the binary from the archive and add it to your system’s PATH.
  3. Create a new file called SamplePolicy.rego and add the following Rego code to define a sample policy:
package example

default allow = false

allow {
input.method = "GET"
input.path = ["api", "values"]
}

This policy specifies that any HTTP GET request to the path /api/values is allowed.

4. Start the OPA server by running the following command in your terminal:

opa run --server --set=plugins.envoy_ext_authz_grpc.addr=:9191 policy.rego

This command starts the Open Policy Agent (OPA) server and configures it to use the Envoy External Authorization gRPC API on port 9191.

5. Verify that the Open Policy Agent (OPA) server is running by visiting the URL http://localhost:9191/health in the web browser or making a request to the /health endpoint using a tool like curl.

Now that we have installed and configured the Open Policy Agent (OPA) server, we can proceed further to integrate it with the ASP.NET Core Web API.

Create a Custom Middleware

To authorize or reject incoming requests based on the defined policies, the next step is to develop a custom middleware. We will create a class called OpaAuthorizationMiddleware, which utilizes the Open Policy Agent (OPA) REST API to evaluate the policies.

Create a file named OpaAuthorizationMiddleware.cs and add the following code to the file:

using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public class OpaAuthorizationMiddleware
{
private readonly HttpClient _httpClient;
private readonly string _opaUrl;

public OpaAuthorizationMiddleware(RequestDelegate next, string opaUrl)
{
_httpClient = new HttpClient();
_opaUrl = opaUrl;
}

public async Task InvokeAsync(HttpContext context)
{
// Evaluate the policy using OPA
var input = new
{
method = context.Request.Method,
path = context.Request.Path.Value,
user = context.User?.Identity?.Name ?? "anonymous",
};

var requestContent = new StringContent(JsonConvert.SerializeObject(input), Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(_opaUrl, requestContent);

if (!response.IsSuccessStatusCode)
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
return;
}

var responseBody = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<AuthorizationResult>(responseBody);

// Check if the request is authorized
if (result.IsAuthorized)
{
await _next(context);
}
else
{
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
}

The AuthorizationResult is a custom model that we would define in our own code to represent the result of evaluating the Open Policy Agent (OPA) policy. This model should contain the properties necessary to indicate whether the request is authorized or not.

Here is an example of what the AuthorizationResult model might look like:

public class AuthorizationResult
{
public bool IsAuthorized { get; set; }
public string Reason { get; set; }
}

In this example, the AuthorizationResult model has two properties:

  • IsAuthorized: A boolean value indicating whether the request is authorized or not.
  • Reason: An optional string property that can be used to provide a reason why the request was rejected, such as "insufficient permissions" or "resource not found".

After implementing the OPA policy evaluation logic, the middleware should set the properties accordingly based on the evaluation result. The IsAuthorized property can then be utilized by the middleware to either allow or reject the incoming request. Additionally, if needed, the Reason property can be used to furnish the client with additional information regarding why the request was rejected.

Integrate the Middleware with the Web API

After successfully creating the OpaAuthorizationMiddleware class, the next step is to integrate it with the ASP.NET Core Web API. This can be achieved by adding the middleware to the request pipeline and configuring it to use the OPA endpoint.

In the ConfigureServices method of the Startup.cs file, add the following code:

services.AddAuthorization();

This registers the ASP.NET Core authorization services with the dependency injection container.

Next, in the Configure method, add the following code:

public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<OpaAuthorizationMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

This adds the OpaAuthorizationMiddleware to the request pipeline and configures it to use the Open Policy Agent (OPA) endpoint. The http://localhost:9191/v1/data/example/allow URL corresponds to the example package and allow rule defined in the SamplePolicy.rego file.

Finally, we’ll add the [Authorize] attribute to the controller or action that we want to protect with the Open Policy Agent (OPA) policy. For example:

[Authorize]
[HttpGet]
public IActionResult Get()
{
return Ok("Authorized");
}

This tells the ASP.NET Core authorization middleware to authorize the incoming request before invoking the Get action. If the request is not authorized according to the OPA policy, the middleware will reject the request and return a 403 Forbidden status code.

Conclusion

In this article, we have demonstrated the integration of Open Policy Agent (OPA) with an ASP.NET Core Web API. We illustrated the process of defining a sample policy using Rego language, creating a custom middleware to evaluate the policy, and integrating it with the Web API. This integration empowers us to define and enforce highly intricate authorization policies that are adaptable and customizable to cater to our specific needs.

It is important to note that the example provided in this article is just a basic starting point. In real-world scenarios, we would typically need to define more intricate policies that consider additional factors such as user roles, permissions, and resource ownership. Nonetheless, the fundamental concepts and steps explained in this article should serve as a solid foundation for constructing a more resilient authorization system using Open Policy Agent (OPA) and ASP.NET Core.

--

--

Amit Anand

I am a Full Stack developer with 6 years of experience in wide variety of technologies. In my off work space I am a poet, a cook and a nature lover.