ASP.NET Core Integration

If you are using ASP.NET Core, you can use our integration NuGet package called PolicyServer.Runtime.Client.AspNetCore.

This sets up the runtime client and integrates it with various ASP.NET Core facilities. Use the DI approach:

services.AddPolicyServerRuntimeClient(Configuration.GetSection("PolicyServerRuntimeClient"));

You can then use dependency injection in e.g. controllers to evaluate policies:

public class HomeController : Controller
{
    private readonly IPolicyServerRuntimeClient _client;

    public HomeController(IPolicyServerRuntimeClient client)
    {
        _client = client;
    }

    public async Task<IActionResult> Secure()
    {
        var result = await _client.EvaluateAsync(User);
        return View(result);
    }
}

Dealing with outgoing HTTP Connections

The runtime client makes several outgoing HTTP calls (e.g. for acquiring access tokens and evaluating policies). Every outgoing HTTP call is made using an HTTP client that got created by the ASP.NET Core HttpClientFactory.

This allows for centralized configuration and lifetime management and additional featurers like correlation ID propagation (using the X-Correlation-ID header).

You can also futher customize the HTTP clients used by the runtime library, e.g. adding retry logic via the Polly library:

services.AddPolicyServerRuntimeHttpClient()
    .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
    }));

services.AddPolicyServerRuntimeTokenHttpClient()
    .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(3)
    }));

Note

Internally the runtime library uses two clients - one for requesting access tokens, and one for evaluating policies. They can be customized independently.

Configuring a Proxy

If any of the back-channel clients need to use a proxy, you can configure them e.g. using the following sample snippet:

var handler = new HttpClientHandler
{
    UseProxy = true,
    Proxy = new WebProxy("https://proxy")
};

services.AddPolicyServerRuntimeHttpClient()
    .ConfigurePrimaryHttpMessageHandler(s => handler);

Integration with the Authorization System

ASP.NET Core has a new API to model authorization (see docs).

This can be nicely integrated with PolicyServer policies - e.g. every permission in PolicyServer can automatically turn into a policy name in ASP.NET Core. This can be enabled by making the following change in Startup:

services.AddPolicyServerRuntimeClient(Configuration.GetSection("PolicyServerRuntimeClient"))
    .AddAuthorizationPermissionPolicies();

The following code now automatically checks if the current user has the PerformSurgery permission in PolicyServer:

[Authorize("PerformSurgery")]
public async Task<IActionResult> PerformSurgery()
{
    // details omitted
}

You can also create more sophisticated authorization policies with the ASP.NET authorization system using custom requirements. The following custom requirements models a “prescribe medication” operation:

public class MedicationRequirement : IAuthorizationRequirement
{
    public string MedicationName { get; set; }
    public int Amount { get; set; }
}

By writing an authorization handler, you can utilize both the ASP.NET Core policy system as well as PolicyServer to implement the authorization logic in your application:

public class MedicationRequirementHandler : AuthorizationHandler<MedicationRequirement>
{
    private readonly IPolicyServerRuntimeClient _client;

    public MedicationRequirementHandler(IPolicyServerRuntimeClient client)
    {
        _client = client;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MedicationRequirement requirement)
    {
        var user = context.User; var allowed = false;

        if (await _client.HasPermissionAsync(user, "PrescribeMedication"))
        {
            if (requirement.Amount <= 10) allowed = true;
            else allowed = await _client.IsInRoleAsync(user, "doctor");

            if (allowed || requirement.MedicationName == "placebo")
            {
                context.Succeed(requirement);
            }
        }
    }
}

Passing Route or QueryString Parameters

It is possible that the name of the policy that should be evaluated is dynamic based on parameters to your application.

For example, for this action method:

[HttpGet("Operate/{customer}"))]
public IActionResult Operate(string customer, string hospitalId)
{
    ...
}

The policy that should be evaluated is "/Hospitals/{customer}/{hospitalId}", where the route or query string parameters are dynamically substituted into the policy path. Dynamically constructing the path can always be done imperatively, but it might be desirable to use the declarative [Authorize] syntax instead. PolicyServer’s [Authorize] attribute support allows using a special syntax for the name passed to the [Authorize] attribute such that route and query string parameter values will be automatically passed as the path to the policy to be evaluated.

The syntax is in the form "PermissionName:{ParamName}/{OtherParamName}". Note the : character separates the permission name from the policy path. The {name} placeholders indicate route or query string parameter values to substitute into the policy path.

For example:

[HttpGet("Operate/{customer}"))]
[Authorize("PerformSurgery:{customer}/{hospitalId}")]
public IActionResult Operate(string customer, string hospitalId)
{
    ...
}

For the request /Operate/CustomerA?hospitalId=5, the [Authorize] attribute will evaluate the CustomerA/5 policy and require a require a PerformSurgery permission in the results.

Note

Don’t forget that the base policy name from the configuration file will be prepended to the path of the policy when using the [Authorize] attribute in the above examples.

Integration with Legacy Code

If you have existing investments in the standard .NET claims API or role-based authorization, you can turn PolicyServer roles and permissions into claims automatically. This will make them compatible with the standard .NET APIs.

By adding the following service to your ASP.NET Core application, the current user will get evaluated automatically against the base policy on every request. The outcome will be transformed into claims of type role and permission and added as a separate identity tot the current user principal:

public void ConfigureServices(IServiceCollection services)
{
    services.AddPolicyServerClaimsTransformation();
}

This allows you e.g. to write the following code:

// if you are using the UsePolicyServerClaimsTransformation middleware, roles are mapped to claims
// this allows using the classic authorize attribute
[Authorize(Roles = "nurse")]
public async Task<IActionResult> NursesOnly()
{
    // can also use the standard .NET APIs
    var permissions = User.FindAll("permission");
    var isNurse = User.HasClaim("role", "nurse");

    return View("success");
}