OpenPolicyAgent.Opa.Authorization
1.0.2
See the version list below for details.
dotnet add package OpenPolicyAgent.Opa.Authorization --version 1.0.2
NuGet\Install-Package OpenPolicyAgent.Opa.Authorization -Version 1.0.2
<PackageReference Include="OpenPolicyAgent.Opa.Authorization" Version="1.0.2" />
<PackageVersion Include="OpenPolicyAgent.Opa.Authorization" Version="1.0.2" />
<PackageReference Include="OpenPolicyAgent.Opa.Authorization" />
paket add OpenPolicyAgent.Opa.Authorization --version 1.0.2
#r "nuget: OpenPolicyAgent.Opa.Authorization, 1.0.2"
#:package OpenPolicyAgent.Opa.Authorization@1.0.2
#addin nuget:?package=OpenPolicyAgent.Opa.Authorization&version=1.0.2
#tool nuget:?package=OpenPolicyAgent.Opa.Authorization&version=1.0.2
OpenPolicyAgent.Opa.Authorization
A .NET NuGet package that provides attribute-based authorization for ASP.NET Core using Open Policy Agent (OPA).
Features
- Attribute-based authorization: Use
[OpaAuthorize]attribute on controllers and methods - Seamless ASP.NET Core integration: Works with existing authentication and authorization infrastructure
- Policy-based decisions: Delegate authorization logic to OPA policies
- Flexible configuration: Configure OPA URL, policy paths, and custom context data
- Compatible with OPA ecosystem: Built on top of the official OpenPolicyAgent.Opa package
Installation
Install the package via NuGet:
dotnet add package OpenPolicyAgent.Opa.Authorization
Quick Start
1. Configure OPA Authorization
In your Program.cs or Startup.cs:
using OpenPolicyAgent.Opa.Authorization;
var builder = WebApplication.CreateBuilder(args);
// Add authentication (required)
builder.Services.AddAuthentication(/* your authentication configuration */);
// Add OPA authorization
builder.Services.AddOpaAuthorization(options =>
{
options.OpaUrl = "http://localhost:8181";
options.DefaultPolicyPath = "authz";
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
2. Use the OpaAuthorize Attribute
Apply the [OpaAuthorize] attribute to your controllers or actions:
using Microsoft.AspNetCore.Mvc;
using OpenPolicyAgent.Opa.Authorization;
[ApiController]
[Route("api/[controller]")]
public class DocumentsController : ControllerBase
{
// Uses the default policy path configured in options
[OpaAuthorize]
[HttpGet]
public IActionResult GetAll()
{
return Ok(new[] { "document1", "document2" });
}
// Uses a custom policy path for this specific action
[OpaAuthorize("authz/documents")]
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
return Ok($"document{id}");
}
// Includes extra information (available as input.context.metadata in OPA)
[OpaAuthorize("authz/documents", "AdminOperation")]
[HttpPost]
public IActionResult Create([FromBody] object document)
{
return Created("", document);
}
}
3. Create OPA Policy
Create a Rego policy file (e.g., policy.rego) using modern Rego syntax:
package authz
import rego.v1
# Default deny - always deny by default for security
default allow := false
# Allow GET requests to /api/documents for authenticated users
allow if {
input.action.name == "GET"
startswith(input.resource.id, "/api/documents")
input.subject.id != ""
}
# Allow POST requests only for admin users
allow if {
input.action.name == "POST"
startswith(input.resource.id, "/api/documents")
has_role("admin")
}
# Helper function to check if user has a specific role
has_role(role) if {
some claim in input.subject.claims
claim.type == "role"
claim.value == role
}
Note: This example uses modern Rego syntax with:
import rego.v1for future-proof policiesifkeyword for clearer rule definitions:=for explicit assignmentsomefor explicit iteration
For more information, see the OPA Policy Language documentation and Rego Cheat Sheet.
4. Run OPA Server
Start an OPA server with your policy:
opa run --server --addr localhost:8181 policy.rego
Configuration Options
OpaAuthorizationOptions
builder.Services.AddOpaAuthorization(options =>
{
// OPA server URL (default: http://localhost:8181)
options.OpaUrl = "http://localhost:8181";
// Default policy path to evaluate (optional)
// This should be the package path (e.g., "authz") not the rule path (e.g., "authz/allow")
options.DefaultPolicyPath = "authz";
// Preferred language key for access denial reasons (default: "en")
options.ReasonKey = "en";
// Allow unauthenticated requests (default: false)
options.AllowUnauthenticated = false;
// Include authorization token in OPA input (default: false)
// When enabled, the Authorization header value is included in input.subject.token
options.IncludeAuthorizationToken = false;
// Request timeout for OPA calls (default: 30 seconds)
options.RequestTimeout = TimeSpan.FromSeconds(30);
// Require HTTPS for OPA URL (default: false)
// When enabled, non-HTTPS URLs will cause validation errors
options.RequireHttps = false;
// Control which headers are sent to OPA (default: true)
options.IncludeHeaders = true;
// Customize which headers to exclude (default includes: Authorization, Cookie, X-API-Key, X-Auth-Token)
options.ExcludedHeaders.Add("Custom-Sensitive-Header");
// Or clear and start fresh:
// options.ExcludedHeaders.Clear();
});
Environment Variable Configuration
You can also configure the OPA URL via environment variable:
export OPA_URL=http://opa-server:8181
Health Check
Add OPA connectivity health checks to your application:
builder.Services.AddHealthChecks()
.AddOpaHealthCheck(
name: "opa",
tags: new[] { "ready", "opa" });
// In your pipeline
app.MapHealthChecks("/health/ready");
The health check verifies that the OPA server is reachable and responding.
Custom Context Data Provider
Inject additional context data into OPA evaluation:
public class CustomContextDataProvider : IOpaContextDataProvider
{
public object GetContextData(HttpContext context)
{
return new
{
tenant_id = context.Request.Headers["X-Tenant-Id"].ToString(),
request_time = DateTime.UtcNow
};
}
}
// Register the provider
builder.Services.AddOpaContextDataProvider<CustomContextDataProvider>();
This data will be available under input.context.data in your OPA policy.
OPA Input Schema
The package sends the following input to OPA:
{
"subject": {
"type": "aspnetcore_authentication",
"id": "<user identity name>",
"claims": [/* array of user claims */],
"token": "<authorization header value, if IncludeAuthorizationToken is enabled>"
},
"resource": {
"type": "endpoint",
"id": "<request path>"
},
"action": {
"name": "<HTTP method>",
"protocol": "<HTTP protocol>",
"headers": {/* request headers */}
},
"context": {
"type": "http",
"host": "<request host>",
"ip": "<remote IP address>",
"port": <remote port>,
"data": {/* custom context data, if provider registered */},
"metadata": "<extra information from attribute, if provided>"
}
}
Note:
- The
tokenfield insubjectis only included whenIncludeAuthorizationTokenis set totruein the options and an Authorization header is present. - The
metadatafield is only included when using[OpaAuthorize("policy/path", "Extra Information")]with the second parameter. - The
headersfield inactionrespects theIncludeHeadersandExcludedHeadersconfiguration. By default, sensitive headers like Authorization, Cookie, X-API-Key, and X-Auth-Token are excluded.
OPA Response Schema
The package expects the following response from OPA:
{
"allow": true,
"reason": "Access granted" // or {"en": "Access granted", "es": "Acceso concedido"}
}
Important:
- Your Rego policy must define an
allowrule that returns a boolean value - The
reasonfield is optional but recommended for providing denial explanations - When using OPA's REST API, these fields appear under
result(e.g.,{"result": {"allow": true, "reason": "..."}}) - The .NET code automatically extracts values from the
resultobject - Additional fields in your policy response (like
decision_log,debug_info) are ignored but won't cause errors
Examples
See the samples directory for complete working examples, including:
- Basic authorization with role-based access control
- Document-specific policies
- Debug policy with comprehensive logging - See
samples/SampleWebApi/policies/debug_policy.regofor an example that logs complete call information for troubleshooting
Debugging OPA Policies
Using the Debug Policy
The sample includes a comprehensive debug policy (debug_policy.rego) that logs all evaluation details:
package authz.debug
import rego.v1
# Returns comprehensive decision log with all input data
decision_log := {
"timestamp": time.now_ns(),
"subject": {
"id": input.subject.id,
"claims_count": count(input.subject.claims),
},
"resource": {
"id": input.resource.id,
"type": input.resource.type,
},
"action": {
"name": input.action.name,
},
"evaluation": {
"matched_rules": matched_rules,
"user_roles": user_roles,
"is_authenticated": is_authenticated,
"is_admin": is_admin,
},
}
Note: The decision_log does not include allow and reason to avoid circular references. These are separate top-level fields in the policy response.
To use the debug policy:
Configure your endpoint to use the debug policy path:
[OpaAuthorize("authz/debug")] [HttpGet] public IActionResult GetDocument() { ... }Query the decision log alongside your allow decision:
curl -X POST http://localhost:8181/v1/data/authz/debug \ -H 'Content-Type: application/json' \ -d @input.jsonEnable OPA decision logging for automatic audit trails:
opa run --server --addr localhost:8181 \ --set decision_logs.console=true \ policy.rego
Best Practices for Debugging
Use
print()statements during development:allow if { print("Checking user:", input.subject.id) print("User roles:", user_roles) has_role("admin") }Test policies with sample inputs:
opa eval -d policy.rego -i input.json 'data.authz.allow'Enable verbose logging in this package:
{ "Logging": { "LogLevel": { "OpenPolicyAgent.Opa.Authorization": "Debug" } } }
Security Considerations
Header Filtering
By default, sensitive headers are excluded from being sent to OPA:
AuthorizationCookieX-API-KeyX-Auth-Token
You can customize this list via options.ExcludedHeaders or disable header inclusion entirely with options.IncludeHeaders = false.
HTTPS Enforcement
For production environments, consider enabling HTTPS enforcement:
options.RequireHttps = true;
This ensures that the OPA URL uses HTTPS, preventing credentials or sensitive data from being transmitted over unencrypted connections.
Token Handling
When IncludeAuthorizationToken is enabled, be aware that the authorization token will be sent to OPA. Ensure:
- OPA is running in a secure environment
- Network communication to OPA is encrypted (use HTTPS)
- OPA policies do not log tokens or include them in policy decision responses
Logging
The package uses structured logging at different levels:
Trace: Detailed OPA evaluation informationDebug: OPA input/output (may contain sensitive data - disable in production)Information: Authorization decisionsWarning: Configuration or connectivity issuesError: Failures and exceptions
Important: Debug and Trace level logging may expose sensitive information. Configure log levels appropriately for your environment.
Troubleshooting
OPA Server Connection Issues
Symptom: Authorization always fails with HTTP connection errors.
Solutions:
- Verify OPA server is running:
curl http://localhost:8181/health - Check the OPA URL configuration
- Ensure network connectivity between your app and OPA
- Use health checks to monitor OPA connectivity:
builder.Services.AddHealthChecks().AddOpaHealthCheck()
Policy Evaluation Errors
Symptom: "Error evaluating OPA policy" in logs.
Solutions:
- Verify the policy path is correct (e.g., "authz" for package authz with allow and reason rules)
- Check that the policy exists in OPA:
curl http://localhost:8181/v1/policies - Test the policy directly with curl:
curl -X POST http://localhost:8181/v1/data/authz \ -H 'Content-Type: application/json' \ -d '{"input": {...}}' - Enable debug logging to see the exact input being sent to OPA
Timeout Issues
Symptom: "Timeout communicating with OPA server" errors.
Solutions:
- Increase the request timeout:
options.RequestTimeout = TimeSpan.FromSeconds(60) - Optimize your OPA policies to execute faster
- Check OPA server performance and resource availability
- Consider using OPA policy compilation for complex policies
Unexpected Authorization Denials
Symptom: Users are denied access when they should be allowed.
Solutions:
- Enable debug logging to see the OPA input and response
- Test the policy with the exact input being sent
- Verify claims are being populated correctly
- Check if
AllowUnauthenticatedshould be enabled - Verify the policy is returning
{"allow": true}(not{"result": true})
"Could not convert bool result to type OpaResponse" Error
Symptom: "Could not convert bool result to type OpenPolicyAgent.Opa.Authorization.OpaResponse" error in logs.
Cause: This occurs when the policy path points to a specific rule (e.g., authz/allow) instead of the package (e.g., authz).
Solution:
Change your policy path from
authz/allowtoauthz:options.DefaultPolicyPath = "authz"; // Correct - queries the package // NOT: options.DefaultPolicyPath = "authz/allow"; // Wrong - queries only the ruleYour OPA policy package should contain
allowand optionallyreasonfields:package authz default allow := false allow if { # your rules } reason["en"] := "Access denied" if { not allow }When querying
/v1/data/authz, OPA returns:{"result": {"allow": true, "reason": {"en": "..."}}}But when querying
/v1/data/authz/allow, OPA returns:{"result": true} // Just the boolean value
The library expects the full object structure with allow and reason fields, not just a boolean.
Headers Not Available in OPA
Symptom: Headers are missing in the OPA policy input.
Solutions:
- Check if
IncludeHeadersis set totrue(default) - Verify the header is not in the
ExcludedHeaderslist - Remember that sensitive headers are excluded by default for security
Performance Considerations
Caching
The package does not include built-in caching of OPA decisions. For high-traffic applications, consider:
- Using OPA's decision logging and caching features
- Deploying OPA as a sidecar for minimal network latency
- Implementing application-level caching if appropriate for your use case
Policy Optimization
- Keep policies focused and efficient
- Use policy compilation for complex policies
- Consider partial evaluation for data filtering scenarios
- Monitor OPA performance metrics
Dependencies
This package is built on top of:
- OpenPolicyAgent.Opa - Official OPA C# SDK
- ASP.NET Core 8.0
Related Packages
- OpenPolicyAgent.Opa.AspNetCore - Middleware-based OPA authorization for ASP.NET Core
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- OpenPolicyAgent.Opa (>= 1.6.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.