Authlink.Policy.AspNetCore 1.5.0

dotnet add package Authlink.Policy.AspNetCore --version 1.5.0
                    
NuGet\Install-Package Authlink.Policy.AspNetCore -Version 1.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Authlink.Policy.AspNetCore" Version="1.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Authlink.Policy.AspNetCore" Version="1.5.0" />
                    
Directory.Packages.props
<PackageReference Include="Authlink.Policy.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Authlink.Policy.AspNetCore --version 1.5.0
                    
#r "nuget: Authlink.Policy.AspNetCore, 1.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Authlink.Policy.AspNetCore@1.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Authlink.Policy.AspNetCore&version=1.5.0
                    
Install as a Cake Addin
#tool nuget:?package=Authlink.Policy.AspNetCore&version=1.5.0
                    
Install as a Cake Tool

Authlink.Policy.AspNetCore

ASP.NET Core integration for Authlink policy-based authorization. Provides attributes, authorization handlers, and middleware for seamless integration with the ASP.NET Core authorization framework.

Installation

dotnet add package Authlink.Policy.AspNetCore

Quick Start

1. Register Services

// Program.cs
builder.Services.AddHttpContextAccessor();

// Register the policy client (from Authlink.Policy.Client.Http)
builder.Services.AddAuthlinkPolicyClient(options =>
{
    options.BaseUrl = builder.Configuration["PolicyApi:BaseUrl"];
});

// Register policy authorization
builder.Services.AddAuthlinkPolicyAuthorization(options =>
{
    options.SubjectClaimTypes = new[] { "sub", "user_id" };
    options.DefaultResourceKey = "tenant";
});

// Register your resource attribute resolvers
builder.Services.AddScoped<IResourceAttributeResolver, UserResourceAttributeResolver>();

2. Use the PolicyAuthorize Attribute

// Simple permission check
[PolicyAuthorize("reports:view")]
[HttpGet("reports")]
public async Task<IActionResult> GetReports()
{
    // Authorization already handled
    return Ok(await _reportService.GetReportsAsync());
}

// With resource attributes
[PolicyAuthorize("user:update", ResourceKey = "user", ResourceIdProperty = "id")]
[HttpPut("users/{id}")]
public async Task<IActionResult> UpdateUser(Guid id, UpdateUserRequest request)
{
    // Policy evaluated with user's resource attributes
    return Ok(await _userService.UpdateAsync(id, request));
}

Features

PolicyAuthorize Attribute

The [PolicyAuthorize] attribute provides declarative policy-based authorization:

Property Description
Permission Required. The permission key (e.g., "user:update")
ResourceKey Optional. The resource type for attribute resolution
ResourceIdProperty Optional. Property name containing the resource ID
ResourceIdSource Where to find the resource ID (Auto, Route, Query, Form, Body)
ResourceIdSource Values
Value Description
Auto Tries route → query → form → body (in order)
Route Extract from route parameters (e.g., /users/{id})
Query Extract from query string (e.g., ?id=123)
Form Extract from form data
Body Extract from JSON request body (requires request buffering)

Resource Attribute Resolution

Implement IResourceAttributeResolver to provide resource attributes for policy evaluation:

public class UserResourceAttributeResolver : IResourceAttributeResolver
{
    private readonly IUserRepository _userRepository;

    public string ResourceKey => "user";

    public async Task<Dictionary<string, string>> ResolveAsync(
        object resourceId,
        CancellationToken cancellationToken = default)
    {
        var userId = (Guid)resourceId;
        var user = await _userRepository.GetByIdAsync(userId, cancellationToken);

        return new Dictionary<string, string>
        {
            ["tenant-id"] = user.TenantId.ToString(),
            ["department"] = user.Department ?? "",
            ["status"] = user.Status.ToString()
        };
    }
}

Configuration Options

services.AddAuthlinkPolicyAuthorization(options =>
{
    // Claim types to search for subject ID (in order)
    options.SubjectClaimTypes = new[] { "sub", "user_id" };

    // Default resource key when not specified on attribute
    options.DefaultResourceKey = "tenant";

    // Throw exceptions instead of returning 401/403
    options.ThrowOnMissingSubject = false;
    options.ThrowOnPolicyError = false;

    // Allow missing resource resolvers (logs warning, proceeds without attributes)
    options.AllowMissingResourceResolver = false;

    // Static audience (API resource) ID sent on every decision request, enabling
    // audience-scoped policy statements to match (default: null, not sent)
    options.AudienceId = Guid.Parse("550e8400-e29b-41d4-a716-446655440000");

    // Claim types to search for tenant ID, purpose, and client ID (in order).
    // Absent claims yield null; non-GUID tenant/client values are treated as
    // absent and logged at warning level.
    options.TenantClaimTypes = new[] { "tenant_id" };
    options.PurposeClaimTypes = new[] { "purpose" };
    options.ClientIdClaimTypes = new[] { "azp", "client_id" };
});

Per-Request Decision Caching

Decisions are cached in HttpContext.Items for the duration of a request, keyed on the permission key and the resolved resource attributes. Stacked [PolicyAuthorize] attributes, repeated checks for the same permission, and combined attribute + middleware checks share a single PDP call per unique permission/resource pair. Both Allow and Deny decisions are cached; transport errors are never cached, so a failed call is retried by the next check. The cache never outlives the request — policy changes are always visible on the next request.

Custom Subject ID Provider

Override the default subject ID extraction by implementing ISubjectIdProvider:

public class CustomSubjectIdProvider : ISubjectIdProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public Task<Guid?> GetSubjectIdAsync(CancellationToken cancellationToken = default)
    {
        var context = _httpContextAccessor.HttpContext;
        if (context?.Items["CurrentUserId"] is Guid userId)
        {
            return Task.FromResult<Guid?>(userId);
        }
        return Task.FromResult<Guid?>(null);
    }
}

// Register before AddAuthlinkPolicyAuthorization
services.AddScoped<ISubjectIdProvider, CustomSubjectIdProvider>();

Request Body Extraction

To extract resource IDs from the JSON request body (when using ResourceIdSource.Body or ResourceIdSource.Auto), you must enable request buffering in your middleware pipeline:

// Program.cs - Add before app.UseAuthorization()
app.Use(async (context, next) =>
{
    context.Request.EnableBuffering();
    await next();
});

This allows the authorization handler to read the request body while still allowing downstream handlers (like model binding) to read it as well.

Middleware-Based Authorization

As an alternative to attribute-based authorization, you can use route-based policy authorization middleware. This approach is useful when you prefer convention-based authorization or need to apply policies globally.

Register Middleware Services

// Program.cs
builder.Services.AddHttpContextAccessor();

// Register the policy client
builder.Services.AddAuthlinkPolicyClient(options =>
{
    options.BaseUrl = builder.Configuration["PolicyApi:BaseUrl"];
});

// Register middleware services
builder.Services.AddAuthlinkPolicyMiddleware(options =>
{
    // Routes that bypass authorization entirely
    options.BypassPatterns.Add("/health");
    options.BypassPatterns.Add("/swagger/**");

    // Route-to-permission mappings
    options.Mappings.Add(new RoutePolicyMapping
    {
        Method = "GET",
        Pattern = "/api/users",
        PermissionKey = "user:list"
    });

    options.Mappings.Add(new RoutePolicyMapping
    {
        Method = "GET",
        Pattern = "/api/users/{id}",
        PermissionKey = "user:read",
        ResourceKey = "user",
        ResourceIdRouteParameter = "id"
    });

    options.Mappings.Add(new RoutePolicyMapping
    {
        Method = "PUT",
        Pattern = "/api/users/{id}",
        PermissionKey = "user:update",
        ResourceKey = "user",
        ResourceIdRouteParameter = "id"
    });

    // Deny access to unmapped routes (optional, default: false)
    options.DenyByDefault = false;
});

Add Middleware to Pipeline

var app = builder.Build();

app.UseAuthentication();
app.UsePolicyAuthorization();  // Add after authentication
app.UseRouting();
app.MapControllers();

app.Run();

Route Pattern Syntax

Pattern Description Example
/api/users Exact match Matches /api/users only
/api/users/{id} Route parameter Matches /api/users/123, /api/users/abc
/api/* Single segment wildcard Matches /api/users but not /api/users/123
/api/** Multi-segment wildcard Matches /api/users, /api/users/123/orders

Middleware Configuration Options

builder.Services.AddAuthlinkPolicyMiddleware(
    mappings =>
    {
        mappings.BypassPatterns.Add("/health");
        mappings.DenyByDefault = true;
        mappings.Mappings.Add(new RoutePolicyMapping { ... });
    },
    auth =>
    {
        // Throw exceptions instead of returning 401/403
        auth.ThrowOnMissingSubject = false;
        auth.ThrowOnPolicyError = false;
    });

Custom Policy Mapping Provider

For advanced scenarios, implement IPolicyMappingProvider:

public class DatabasePolicyMappingProvider : IPolicyMappingProvider
{
    private readonly IRoutePermissionRepository _repository;

    public PolicyMapping? GetMapping(string method, string path)
    {
        // Load from database, cache, etc.
        var mapping = _repository.FindMapping(method, path);
        if (mapping == null) return null;

        return new PolicyMapping
        {
            PermissionKey = mapping.Permission,
            ResourceKey = mapping.ResourceType,
            ResourceIdRouteParameter = mapping.ResourceIdParam
        };
    }
}

// Register before AddAuthlinkPolicyMiddleware
services.AddSingleton<IPolicyMappingProvider, DatabasePolicyMappingProvider>();

Requirements

  • .NET 9.0 or later
  • ASP.NET Core 9.0 or later
  • Authlink.Policy.Client.Core (automatically included)
  • IPolicyClient implementation (e.g., Authlink.Policy.Client.Http)
  • Authlink.Policy.Client.Core - Core abstractions and DTOs
  • Authlink.Policy.Client.Http - HTTP client implementation
  • Authlink.Policy.ServiceStack - ServiceStack integration

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.5.0 244 9/17/2026