Skip to content

Configure Authorization

RBAC (Role-Based Access Control) is a widely used authorization model in software applications. It provides a way to control access to resources based on the roles assigned to users. Keycloak, an open-source identity and access management solution, offers robust support for RBAC.

With Keycloak.AuthServices.Authorization, you can configure roles by defining realm roles and resource roles. Realm roles are global roles that apply to the entire realm, while resource roles are specific to a particular client or resource.

Table of Contents:

Require Realm Roles

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy => policy.RequireRealmRoles(KeycloakRoles.Admin)
    );

Require Resource Roles

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization()
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy =>
            policy.RequireResourceRolesForClient(
                "test-client",
                [KeycloakRoles.TestClientRole]
            )
    );

Configure default roles source. The client name is taken from the KeycloakAuthorizationOptions.Resource:

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization(context.Configuration)
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy => policy.RequireResourceRoles(KeycloakRoles.TestClientRole)
    );
json
{
  "Keycloak": {
    "resource": "test-client"
  }
}

IMPORTANT

If you don't configure the default roles source KeycloakException exception will be thrown.

Override default source with KeycloakAuthorizationOptions.RolesResource:

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization(options =>
        options.RolesResource = "test-client"
    )
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy => policy.RequireResourceRoles(KeycloakRoles.TestClientRole)
    );

Note it has more priority over the KeycloakAuthorizationOptions.Resource

Keycloak Role Claims Transformation

Keycloak roles can be automatically transformed to AspNetCore Roles. This feature is disabled by default and is based on KeycloakRolesClaimsTransformation.

Specify KeycloakAuthorizationOptions.EnableRolesMapping to enable it. E.g.:

json
{
  "Keycloak": {
    "EnableRolesMapping": "Realm"
  }
}

Here an example of how to configure realm role:

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization(options =>
    {
        options.EnableRolesMapping = RolesClaimTransformationSource.Realm;
        // Note, this should correspond to role configured with KeycloakAuthenticationOptions
        options.RoleClaimType = KeycloakConstants.RoleClaimType;
    })
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy => policy.RequireRole(KeycloakRoles.Admin)
    );

Here an example of how to configure client role:

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

services
    .AddAuthorization()
    .AddKeycloakAuthorization(options =>
    {
        options.EnableRolesMapping =
            RolesClaimTransformationSource.ResourceAccess;
        options.RolesResource = "test-client";
    })
    .AddAuthorizationBuilder()
    .AddPolicy(
        policyName,
        policy => policy.RequireRole(KeycloakRoles.TestClientRole)
    );

There are three options to determine a source for the roles:

csharp
[Flags]
public enum RolesClaimTransformationSource
{
    /// <summary>
    /// Specifies that no transformation should be applied from the source.
    /// </summary>
    None = 0,

    /// <summary>
    /// Specifies that transformation should be applied to the realm.
    /// </summary>
    Realm = 1 << 0,

    /// <summary>
    /// Specifies that transformation should be applied to the resource access.
    /// </summary>
    ResourceAccess = 1 << 1,

    /// <summary>
    /// Specifies that transformation should be applied to all sources.
    /// </summary>
    All = Realm | ResourceAccess
}

Here is an example of decoded JWT token:

json
{
  "exp": 1714057504,
  "iat": 1714057204,
  "jti": "7250d2a9-e5a1-442f-9e76-5e6b78bb2760",
  "iss": "http://localhost:8080/realms/Test",
  "aud": [
    "test-client",
    "account"
  ],
  "sub": "bf0b3371-ccdc-44f6-8861-ce25cbfcac39",
  "typ": "Bearer",
  "azp": "test-client",
  "session_state": "563332d2-111a-4ef2-b6a0-ebc1d3ae9a1e",
  "acr": "1",
  "allowed-origins": [
    "/*"
  ],
  "realm_access": {
    "roles": [
      "default-roles-test",
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "test-client": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "profile email",
  "sid": "563332d2-111a-4ef2-b6a0-ebc1d3ae9a1e",
  "email_verified": false,
  "name": "Test Test",
  "preferred_username": "test",
  "given_name": "Test",
  "family_name": "Test",
  "email": "test@test.com"
}

If we specify KeycloakAuthorizationOptions.EnableRolesMapping = RolesClaimTransformationSource.Realm the roles are taken from $token.realm_access.roles.

Result = ["default-roles-test","offline_access","uma_authorization"]

If we specify KeycloakAuthorizationOptions.EnableRolesMapping = RolesClaimTransformationSource.ResourceAccess and KeycloakAuthorizationOptions.RolesResource="test-client" the roles are taken from $token.realm_access.test-client.roles.

Result = ["manage-account","manage-account-links","view-profile"]

See below the table for possible combinations:

EnableRolesMappingRolesResourceResult
RealmN/A["default-roles-test","offline_access","uma_authorization"]
ResourceAccesstest-client["manage-account","manage-account-links","view-profile"]
Alltest-client["default-roles-test","offline_access","uma_authorization","manage-account","manage-account-links","view-profile"]

The target claim can be configured KeycloakAuthorizationOptions.RoleClaimType, the default value is "role".