bugfix/AB#32738 - ABP Cached Permission Checking with Keycloak OIDC (ARCH-SPIKE - DO NOT MERGE)#2370
bugfix/AB#32738 - ABP Cached Permission Checking with Keycloak OIDC (ARCH-SPIKE - DO NOT MERGE)#2370plavoie-BC wants to merge 8 commits into
Conversation
Co-authored-by: Copilot <copilot@github.com>
|
Here's a summary of what was done: Changes MadeFiles deleted:
Files created:
Files modified:
Estimated cookie reduction: ~14KB → ~2-3KB (from 146 claims to ~20 claims) Key prerequisite: Ensure permissions are correctly assigned to roles in the |
There was a problem hiding this comment.
Pull request overview
This PR is an architecture spike to migrate permission checking from “permissions stamped into the auth cookie” to ABP’s default server-side permission resolution (Redis-cached IPermissionStore) while keeping Keycloak OIDC authentication.
Changes:
- Removes the custom cookie-claim-based
IPermissionCheckerimplementation and the permission-claim stamping during login. - Switches login claim stamping to ABP-standard claim types (
AbpClaimTypes.UserId, addsAbpClaimTypes.Role). - Simplifies policy registration to rely on ABP’s permission policy provider, keeping only custom composite policies via new authorization requirements/handlers.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs | Removes per-permission policy registrations; registers only composite/custom policies using new requirements. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PermissionChecker.cs | Deletes the custom claim-based permission checker so ABP’s default checker can take over. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs | Stops stamping permissions onto principals; adds AbpClaimTypes.Role alongside the existing role claim type. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginBase.cs | Changes stamped user-id claim from legacy "UserId" to AbpClaimTypes.UserId. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs | Removes host-admin permission stamping during login. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/IdentityExtensionMethods.cs | Removes extension methods used for adding permission claims. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs | Updates custom current-user lookup to read AbpClaimTypes.UserId. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/Authorization/RoleOrPermissionAuthorizationHandler.cs | Adds requirement/handler for “role OR permission” composite policies. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/Authorization/PermissionOrAuthorizationHandler.cs | Adds requirement/handler for “any-of these permissions” composite policies. |
Comments suppressed due to low confidence (1)
applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs:76
- This handler no longer stamps any baseline permissions (e.g., the previously hard-coded
IdentityPermissions.UserLookup.Default). If the equivalent grants are not present inAbpPermissionGrantsfor the user’s roles, calls to identity user lookup (used by assignee selection and other UI flows viaIIdentityUserIntegrationService) will start failing with authorization errors. Ensure the needed permissions are granted via role/user permission seeding before relying on ABP’s server-side permission checks.
using (CurrentTenant.Change(userTenantAccount.TenantId))
{
var userRoles = await IdentityUserRepository.GetRolesAsync(userTenantAccount.Id);
if (userRoles != null)
{
foreach (var role in userRoles)
{
var dbRole = await IdentityRoleManager.GetByIdAsync(role.Id);
principal.AddClaim(UnityClaimsTypes.Role, dbRole.Name);
principal.AddClaim(AbpClaimTypes.Role, dbRole.Name);
}
}
}
AssignDefaultClaims(validatedTokenContext.Principal!, userTenantAccount.DisplayName ?? string.Empty, userTenantAccount.Id);
validatedTokenContext.Principal!.AddClaim(AbpClaimTypes.TenantId, userTenantAccount.TenantId?.ToString() ?? Guid.Empty.ToString());
| protected static void AssignDefaultClaims(ClaimsPrincipal claimsPrinicipal, string displayName, Guid userId) | ||
| { | ||
| claimsPrinicipal.AddClaim("DisplayName", displayName); | ||
| claimsPrinicipal.AddClaim("UserId", userId.ToString()); | ||
| claimsPrinicipal.AddClaim(AbpClaimTypes.UserId, userId.ToString()); | ||
| claimsPrinicipal.AddClaim("Badge", Utils.CreateUserBadge(displayName)); | ||
| } |
| UserTenantAccountDto userTenantAccount; | ||
| if (!AdminHasUserAccount(userTenantAccounts)) | ||
| { | ||
| // Create Host Account | ||
| userTenantAccount = await CreateAdminAccountAsync(validatedTokenContext, idp); | ||
| } | ||
| else | ||
| { | ||
| userTenantAccount = userTenantAccounts.First(s => s.TenantId == null); | ||
| } | ||
|
|
||
| AssignAdminHostPermissions(validatedTokenContext.Principal!); | ||
| AssignDefaultClaims(validatedTokenContext.Principal!, userTenantAccount.DisplayName ?? string.Empty, userTenantAccount.Id); | ||
| return userTenantAccount; | ||
| } |
| protected override async Task HandleRequirementAsync( | ||
| AuthorizationHandlerContext context, | ||
| PermissionOrRequirement requirement) | ||
| { | ||
| foreach (var permission in requirement.Permissions) | ||
| { | ||
| if (await _permissionChecker.IsGrantedAsync(context.User, permission)) | ||
| { | ||
| context.Succeed(requirement); | ||
| return; | ||
| } | ||
| } |
Co-authored-by: Copilot <copilot@github.com>
Remediation Plan: ITAdministrator and ITOperations - Seed Host Admin Permission GrantsExtend the existing Steps Phase 1 — Domain (PermissionGrantsDataSeeder)
Phase 2 — Admin Login Handler (parallel)
Phase 3 — Verification
Key facts:
Risks:
Ready for your review. Should I proceed with implementation? |
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
This PR is an architecture spike to migrate Unity’s authorization from “permissions stamped into the auth cookie as claims” to ABP’s standard server-side permission evaluation via IPermissionChecker/IPermissionStore (Redis-cached), reducing cookie size and aligning with ABP permission patterns.
Changes:
- Removes permission-claim stamping at login and deletes the custom claim-based
PermissionChecker. - Simplifies authorization policy registration to rely on ABP’s permission policy provider, while adding custom composite handlers for “role OR permission” and “any-of permissions” scenarios.
- Adds/updates seed logic and tests to support ABP-standard claim types (notably
AbpClaimTypes.UserIdandAbpClaimTypes.Role) and composite authorization behaviors.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PolicyRegistrant.cs | Drops explicit claim-based permission policies and registers only composite policies using new requirements/handlers. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/Authorization/RoleOrPermissionAuthorizationHandler.cs | Adds an authorization requirement/handler for role-or-permission policies. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/Authorization/PermissionOrAuthorizationHandler.cs | Adds an authorization requirement/handler for “any granted permission in set” policies (batch API). |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginBase.cs | Switches to AbpClaimTypes.UserId while retaining legacy UserId claim. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cs | Stops loading/stamping permissions into the principal; adds ABP role claims. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cs | Stops stamping admin host permissions; adds ABP role claim for IT admin. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/CurrentUser.cs | Updates user id resolution to read AbpClaimTypes.UserId. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Domain/Permissions/PermissionGrantsDataSeeder.cs | Adds host-level permission seeding intended to replace login-time permission stamping. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Application/Assessments/AssessmentAuthorizationHandler.cs | Updates user id claim lookup to prefer AbpClaimTypes.UserId with legacy fallback. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/PermissionChecker.cs | Deletes the custom claim-based IPermissionChecker implementation. |
| applications/Unity.GrantManager/src/Unity.GrantManager.Web/Identity/IdentityExtensionMethods.cs | Deletes permission-claim helper extensions no longer used. |
| applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Identity/Authorization/RoleOrPermissionAuthorizationHandlerTests.cs | Adds unit tests for role-or-permission handler behavior. |
| applications/Unity.GrantManager/test/Unity.GrantManager.Web.Tests/Identity/Authorization/PermissionOrAuthorizationHandlerTests.cs | Adds unit tests for “any-of” permission handler using batch permission checks. |
| applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/Assessments/FindUserIdirIdTests.cs | Adds tests covering new/legacy claim fallback behavior for user id extraction. |
| // ITOperations host-level permissions (previously stamped at login) | ||
| await _permissionDataSeeder.SeedAsync(RolePermissionValueProvider.ProviderName, IdentityConsts.ITOperationsRoleName, | ||
| [ | ||
| GrantManagerPermissions.Endpoints.ManageEndpoints, |
| // Simple permission-based policies (e.g., [Authorize("PermissionName")]) are handled | ||
| // automatically by ABP's AbpAuthorizationPolicyProvider + PermissionRequirementHandler. | ||
| // Only register custom composite policies here. |
| var identity = new ClaimsIdentity("TestAuth"); | ||
| identity.AddClaim(new Claim(ClaimTypes.Role, roleName)); | ||
| return new ClaimsPrincipal(identity); | ||
| } | ||
|
|
||
| private static ClaimsPrincipal CreateUserWithoutRole() | ||
| { | ||
| var identity = new ClaimsIdentity("TestAuth"); |
| "UnityTenantManagement.Tenants", | ||
| "UnityTenantManagement.Tenants.Create", | ||
| "UnityTenantManagement.Tenants.Update", | ||
| "UnityTenantManagement.Tenants.Delete", | ||
| "AbpTenantManagement.Tenants.ManageFeatures", | ||
| "UnityTenantManagement.Tenants.ManageConnectionStrings", |
…abp-cached-permissions
|



Pull request overview
This PR is an architecture spike to migrate Unity’s authorization from “permissions stamped into the auth cookie as claims” to ABP’s standard server-side permission evaluation via
IPermissionChecker/IPermissionStore(Redis-cached), reducing cookie size and aligning with ABP permission patterns.Changes:
PermissionChecker.AbpClaimTypes.UserIdandAbpClaimTypes.Role) and composite authorization behaviors.Migration Plan: ABP Cached Permission Checking with Keycloak OIDC
Problem
The ASP.NET authentication cookie is ~14KB (4 chunks) because ~100+
Permissionclaims are stamped on theClaimsPrincipalat login time. The customPermissionCheckerreads these claims directly from the cookie, bypassing ABP's built-in Redis-cached permission store.Goal
Use ABP's default
PermissionCheckerwhich resolves permissions server-side viaIPermissionStore(Redis-cached), eliminating permission claims from the cookie while keeping Keycloak as the external OIDC identity provider.Architecture (Current vs Proposed)
Current Flow
Proposed Flow
Steps
Step 1: Add ABP-Standard Claims in Login Handlers
In
IdentityProfileLoginBase.AssignDefaultClaims:principal.AddClaim("UserId", userId.ToString())→principal.AddClaim(AbpClaimTypes.UserId, userId.ToString())In
IdentityProfileLoginUserHandler, when adding roles:principal.AddClaim(AbpClaimTypes.Role, dbRole.Name)alongside the existingUnityClaimsTypes.RoleclaimStep 2: Remove Permission-Stamping Code
In
IdentityProfileLoginUserHandler.Handle():PermissionManager.GetAllForUserAsync()and adds"Permission"claimsAssignDefaultPermissions()andAssignITOperationsPermissions()In
IdentityProfileLoginAdminHandler.Handle():AssignAdminHostPermissions()call and the_adminPermissionsarrayRemove or deprecate:
IdentityExtensionMethods.AddPermission()IdentityExtensionMethods.AddPermissions()Step 3: Remove Custom PermissionChecker
Delete
src/Unity.GrantManager.Web/Identity/PermissionChecker.cs— ABP's built-inPermissionCheckerwill automatically take over (it usesIPermissionValueProviderManagerwithUserPermissionValueProviderandRolePermissionValueProvider).Step 4: Update Custom CurrentUser
In
src/Unity.GrantManager.Web/Identity/CurrentUser.cs:FindUserId()to readAbpClaimTypes.UserIdinstead of the custom"UserId"claim typeStep 5: Verify ABP Role-Based Permission Grants Exist
Ensure that permissions are properly assigned to ABP roles in the
AbpPermissionGrantstable (provider = "R", providerKey = roleName). TheRolePermissionValueProviderwill query these.User-level grants (provider = "U", providerKey = userId) are already being created by the existing
PermissionManagerusage in the admin UI.Step 6: Update JavaScript Permission Checks
Verify that
abp.auth.isGranted()still works — it calls the/api/abp/application-configurationendpoint which usesIPermissionCheckerserver-side. This should work automatically once the custom checker is removed.Trade-offs
Files to Modify
src/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginBase.cssrc/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginUserHandler.cssrc/Unity.GrantManager.Web/Identity/LoginHandlers/IdentityProfileLoginAdminHandler.cssrc/Unity.GrantManager.Web/Identity/CurrentUser.cssrc/Unity.GrantManager.Web/Identity/IdentityExtensionMethods.csFiles to Delete
src/Unity.GrantManager.Web/Identity/PermissionChecker.csRisks & Considerations
client_rolesusage — The customCurrentUser.Rolesreads"client_roles"claims; need to verify nothing else depends on this claim type for authorizationZoneRequirementType.PermissionOnlytag helper likely callsIPermissionChecker— verify it still worksPermissionStorerespectsCurrentTenantautomatically; no change neededUnity.GrantManager.Web.Teststo remove reliance on permission claims in test principals