Skip to content

Protect ASP.NET Core

ASP.NET Core allows you to built policies based on Microsoft.AspNetCore.Authorization.AuthorizationBuilder. Keycloak.AuthServices.Authorization adds Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder extension methods to work with protected resources and configure your polices.

AuthorizationPolicyBuilderExtensions
cs
/// <summary>
/// Adds protected resource requirement to builder. Makes outgoing HTTP requests to Authorization Server.
/// </summary>
/// <param name="builder"></param>
/// <param name="resource"></param>
/// <param name="scope"></param>
/// <returns></returns>
public static AuthorizationPolicyBuilder RequireProtectedResource(
    this AuthorizationPolicyBuilder builder,
    string resource,
    string scope
)
{
    ArgumentNullException.ThrowIfNull(builder);
    ArgumentNullException.ThrowIfNull(resource);
    ArgumentNullException.ThrowIfNull(scope);

    return builder.AddRequirements(new DecisionRequirement(resource, scope));
}

Add to your code

Here is how to use to use protected resource authorization.

cs
services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddKeycloakWebApi(context.Configuration);

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy =>
            policy.RequireProtectedResource(
                resource: "my-workspace",
                scope: "workspace:delete"
            )
    );

services.AddAuthorizationServer(context.Configuration);

Here is an example of how to use registered policies:

cs
var app = builder.Build();

app.UseAuthentication(); 
app.UseAuthorization(); 

app.MapGet("/", () => "Hello World!")
    .RequireAuthorization(policyName);

app.Run();

Here are the assertions from the integration test for this scenario:

cs

await host.Scenario(_ =>
{
    _.Get.Url(RunPolicyByName(policyName));
    _.UserAndPasswordIs(TestUsers.Admin.UserName, TestUsers.Admin.Password);
    _.StatusCodeShouldBe(HttpStatusCode.OK);
});

await host.Scenario(_ =>
{
    _.Get.Url(RunPolicyByName(policyName));
    _.UserAndPasswordIs(TestUsers.Tester.UserName, TestUsers.Tester.Password);
    _.StatusCodeShouldBe(HttpStatusCode.Forbidden);
});

Source code of integration test: tests/Keycloak.AuthServices.IntegrationTests/AuthorizationServerPolicyTests.cs

Validate Multiple Scopes

You can specify multiple scopes to validate against and control comparison by using ScopesValidationMode.

Here is an example for ScopesValidationMode.AllOf:

cs
services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddKeycloakWebApi(context.Configuration);

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy =>
            policy.RequireProtectedResource(
                resource: "my-workspace",
                scopes: ["workspace:delete", "workspace:read"],
                scopesValidationMode: ScopesValidationMode.AllOf
            )
    );

services.AddAuthorizationServer(context.Configuration);

Here is an example for ScopesValidationMode.AnyOf:

cs
services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddKeycloakWebApi(context.Configuration);

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy =>
            policy.RequireProtectedResource(
                resource: "my-workspace",
                scopes: ["workspace:delete", "workspace:read"],
                scopesValidationMode: ScopesValidationMode.AnyOf
            )
    );

services.AddAuthorizationServer(context.Configuration);