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
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddKeycloakWebApi(context.Configuration);
services
.AddAuthorization()
.AddKeycloakAuthorization()
.AddAuthorizationBuilder()
.AddPolicy(
policyName,
policy => policy.RequireRealmRoles(KeycloakRoles.Admin)
);
Require Resource Roles
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
:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddKeycloakWebApi(context.Configuration);
services
.AddAuthorization()
.AddKeycloakAuthorization(context.Configuration)
.AddAuthorizationBuilder()
.AddPolicy(
policyName,
policy => policy.RequireResourceRoles(KeycloakRoles.TestClientRole)
);
{
"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
:
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.:
{
"Keycloak": {
"EnableRolesMapping": "Realm"
}
}
Here an example of how to configure realm role:
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:
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:
[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:
{
"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:
EnableRolesMapping | RolesResource | Result |
---|---|---|
Realm | N/A | ["default-roles-test","offline_access","uma_authorization"] |
ResourceAccess | test-client | ["manage-account","manage-account-links","view-profile"] |
All | test-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".