Authentication
JWT Bearer validation, claims transformation for Keycloak, Entra ID, and AWS Cognito, and OIDC back-channel logout — all wired through the module system.
Package structure
Section titled “Package structure”DirectoryGranit.Authentication.JwtBearer/ Generic JWT Bearer, back-channel logout
- Granit.Authentication.Keycloak Keycloak claims transformation
- Granit.Authentication.EntraId Entra ID roles parsing
- Granit.Authentication.Cognito Cognito groups → roles
| Package | Role | Depends on |
|---|---|---|
Granit.Authentication.JwtBearer | JWT Bearer middleware, back-channel logout | Granit.Security |
Granit.Authentication.Keycloak | Keycloak claims transformation | Granit.Authentication.JwtBearer |
Granit.Authentication.EntraId | Entra ID roles parsing | Granit.Authentication.JwtBearer |
Granit.Authentication.Cognito | Cognito groups → roles | Granit.Authentication.JwtBearer |
[DependsOn(typeof(GranitAuthenticationKeycloakModule))]public class AppModule : GranitModule { }{ "Authentication": { "Authority": "https://keycloak.example.com/realms/my-realm", "Audience": "my-client" }, "Keycloak": { "ClientId": "my-client", "AdminRole": "admin", "RoleClaimsSource": "realm_access" }}[DependsOn(typeof(GranitAuthenticationEntraIdModule))]public class AppModule : GranitModule { }{ "Authentication": { "Authority": "https://login.microsoftonline.com/{tenant-id}/v2.0", "Audience": "api://{client-id}" }}[DependsOn(typeof(GranitAuthenticationCognitoModule))]public class AppModule : GranitModule { }{ "Authentication": { "Authority": "https://cognito-idp.{region}.amazonaws.com/{userPoolId}", "Audience": "{clientId}" }, "Cognito": { "UserPoolId": "eu-west-1_XXXXXXXXX", "ClientId": "my-client-id", "Region": "eu-west-1" }}[DependsOn(typeof(GranitJwtBearerModule))]public class AppModule : GranitModule { }{ "Authentication": { "Authority": "https://idp.example.com", "Audience": "my-api", "RequireHttpsMetadata": true, "NameClaimType": "sub" }}JWT Bearer
Section titled “JWT Bearer”GranitJwtBearerModule registers:
- ASP.NET Core JWT Bearer authentication
CurrentUserService—ICurrentUserServiceimplementation extracting claims fromHttpContextIRevokedSessionStore— distributed cache-backed session revocation
Configuration
Section titled “Configuration”{ "Authentication": { "Authority": "https://idp.example.com/realms/my-realm", "Audience": "my-client", "RequireHttpsMetadata": true, "NameClaimType": "sub", "BackChannelLogout": { "Enabled": true, "EndpointPath": "/auth/back-channel-logout", "SessionRevocationTtl": "01:00:00" } }}| Property | Default | Description |
|---|---|---|
Authority | — | OIDC issuer URL (required) |
Audience | — | Expected aud claim (required) |
RequireHttpsMetadata | true | Enforce HTTPS for metadata endpoint |
NameClaimType | "sub" | Claim used as user identifier |
BackChannelLogout.Enabled | false | Enable OIDC back-channel logout |
BackChannelLogout.EndpointPath | "/auth/back-channel-logout" | Endpoint path |
BackChannelLogout.SessionRevocationTtl | "01:00:00" | How long revoked sessions are remembered |
Back-channel logout
Section titled “Back-channel logout”Provider-agnostic implementation of the OIDC Back-Channel Logout specification. When the IdP sends a logout token, the session is revoked in distributed cache.
// In OnApplicationInitializationapp.MapBackChannelLogout(); // POST /auth/back-channel-logout (anonymous)The endpoint validates the logout token signature against the IdP’s JWKS, extracts the sid
claim, and stores it in IDistributedCache with key granit:revoked-session:{sid}.
Subsequent requests with a revoked sid are rejected by the JWT Bearer events handler.
Keycloak claims transformation
Section titled “Keycloak claims transformation”GranitAuthenticationKeycloakModule post-configures JWT Bearer with Keycloak-specific behavior:
- Extracts roles from
realm_access.rolesorresource_access.{clientId}.roles - Maps them to standard
ClaimTypes.Roleclaims - Registers an
"Admin"authorization policy
// Keycloak JWT payload (simplified){ "realm_access": { "roles": ["admin", "doctor"] }, "resource_access": { "my-client": { "roles": ["manage-patients"] } }}// After transformation → ClaimTypes.Role: "admin", "doctor", "manage-patients"Entra ID claims transformation
Section titled “Entra ID claims transformation”GranitAuthenticationEntraIdModule post-configures JWT Bearer with Entra ID-specific behavior:
- Extracts roles from the v1.0
rolesclaim and the v2.0widsclaim - Maps them to standard
ClaimTypes.Roleclaims
Cognito claims transformation
Section titled “Cognito claims transformation”GranitAuthenticationCognitoModule post-configures JWT Bearer with Cognito-specific behavior:
- Extracts groups from the
cognito:groupsclaim (multiple claims with same type) - Maps them to standard
ClaimTypes.Roleclaims
// Cognito JWT payload — groups appear as repeated claims// "cognito:groups": "admin"// "cognito:groups": "doctors"// After transformation → ClaimTypes.Role: "admin", "doctors"Public API summary
Section titled “Public API summary”| Category | Key types | Package |
|---|---|---|
| Modules | GranitJwtBearerModule, GranitAuthenticationKeycloakModule, GranitAuthenticationEntraIdModule, GranitAuthenticationCognitoModule | — |
| Abstractions | CurrentUserService, IRevokedSessionStore, BackChannelLogoutTokenValidator | Granit.Authentication.JwtBearer |
| Claims | KeycloakClaimsTransformation | Granit.Authentication.Keycloak |
| Claims | EntraIdClaimsTransformation | Granit.Authentication.EntraId |
| Claims | CognitoClaimsTransformation | Granit.Authentication.Cognito |
| Options | JwtBearerAuthOptions, KeycloakOptions, CognitoOptions | — |
| Extensions | AddGranitJwtBearer(), AddGranitKeycloak(), AddGranitCognito(), MapBackChannelLogout() | — |
See also
Section titled “See also”- Authorization — RBAC permissions, dynamic policy provider
- Security — core abstractions (ICurrentUserService, ActorKind)
- Identity — user management, Keycloak Admin API, user cache