Skip to content

API Protection

This page explains how the Platform API enforces authorization using Keycloak Authorization Services. The API delegates all access decisions to Keycloak, keeping authorization logic out of application code.

Setup

Authorization is wired up in AuthorizationConfiguration.cs:

csharp
public static IServiceCollection AddPlatformAuthorization(
    this IServiceCollection services,
    IConfiguration configuration)
{
    services.AddKeycloakAuthorization()
        .AddAuthorizationServer(configuration);

    services.AddAuthorizationBuilder()
        .AddFallbackPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());

    return services;
}

This does two things:

  1. AddKeycloakAuthorization().AddAuthorizationServer(configuration) - Registers Keycloak as the authorization server. The API will call Keycloak's token endpoint to evaluate permissions at runtime. Configuration is read from the Keycloak section in appsettings.json or environment variables.

  2. Fallback policy - Any endpoint that doesn't declare a specific permission still requires an authenticated user. No anonymous access is allowed.

The method is called in Program.cs:

csharp
builder.Services.AddPlatformAuthorization(builder.Configuration);

Configuration

The API requires Keycloak connection settings (server URL, realm, client ID, and client secret). In local development, the .NET Aspire AppHost passes these automatically. For standalone deployments, see Provisioning.

The Permission Type

Permissions are modeled as a record struct in Permission.cs, pairing a resource name with a scope. The PolicyName property (e.g., "gateways:read") is used internally by the Keycloak authorization middleware. An implicit string conversion means Permission values can be used anywhere a policy name string is expected.

Defining Permissions

All permissions are defined as static fields in Permissions.cs, organized by resource (e.g., Permissions.Gateways.Read, Permissions.Assets.Write). Each permission corresponds to a resource + scope combination that must also exist in the Keycloak configuration. The full set can be enumerated via Permissions.All.

Protecting Endpoints

Use RequirePermission() on minimal API endpoints:

csharp
app.MapGet("/api/gateways", GetGateways)
    .RequirePermission(Permissions.Gateways.Read);

app.MapPost("/api/gateways", CreateGateway)
    .RequirePermission(Permissions.Gateways.Write);

app.MapDelete("/api/gateways/{id}", DeleteGateway)
    .RequirePermission(Permissions.Gateways.Delete);

For endpoints requiring multiple permissions, use RequireAllPermissions():

csharp
app.MapPost("/api/workflows/{id}/execute", ExecuteWorkflow)
    .RequireAllPermissions(Permissions.Workflow.Write, Permissions.Assets.Read);

Extension Methods

The extension methods in EndpointAuthorizationExtensions.cs wrap RequireProtectedResource() from the Keycloak.AuthServices.Authorization package. At request time, this sends a permission evaluation request to Keycloak's token endpoint using the UMA (User-Managed Access) protocol.

How It Works at Runtime

When a request hits a protected endpoint:

  1. The ASP.NET Core authorization middleware extracts the bearer token from the request
  2. For each RequireProtectedResource requirement, it sends an RPT (Requesting Party Token) request to Keycloak's token endpoint using the UMA grant type
  3. Keycloak evaluates the request against its configured policies and permissions
  4. If access is granted, the request proceeds; otherwise, a 403 Forbidden is returned

Performance consideration

Each permission check results in a call to Keycloak. The Keycloak.AuthServices library handles token caching to minimize overhead, but be mindful when using RequireAllPermissions() with many permissions on a single endpoint.

Adding a New Permission

See Provisioning for the end-to-end workflow covering both Keycloak configuration and API code.

Important

Permissions must match between the API code and the Keycloak configuration. If you add a RequirePermission() call for a resource/scope that doesn't exist in Keycloak, all requests to that endpoint will be denied.