Summary
Expose direct user-claims management (UserManager.GetClaimsAsync / AddClaimAsync / RemoveClaimAsync / ReplaceClaimAsync) through the Admin module, with an "Claims" tab on the user edit page.
Why we need this
The framework already has a strong custom permission system (SimpleModule.Permissions) for authorization, and that should remain the primary mechanism for "can this user do X". But ASP.NET Identity's claim store has other legitimate uses that the permission system was never meant to cover:
- Domain-level facts about a user that downstream code needs without a database hop, e.g.
tenant_id, department, cost_center, feature_tier, region. These don't fit permission — they're not "is allowed to" but "is".
- Third-party API claims carried over from external logins (Microsoft Graph scopes, Google
hd domain, OIDC groups).
- Custom OpenIddict access-token claims — anything we want to ship inside JWTs has to be a claim on the principal. Today there's no UI to put it there.
- Debugging / support — being able to inspect "what claims will this user actually carry on their next request" is genuinely useful when investigating auth bugs.
UsersDbContext already has the AspNetUserClaims table (it's part of IdentityDbContext), but nothing reads or writes it through a UI. This wires up the missing surface.
How the user (admin) will use it
On /admin/users/{id}/edit, alongside the existing Details / Roles / Sessions / Security tabs, add a Claims tab:
- Tab loads → backend calls
UserManager.GetClaimsAsync(user) → table of Type / Value rows, each with a delete button.
- "Add claim" form: two text inputs (
Type, Value), validation prevents duplicates of the same (Type, Value) pair.
- Edit existing claim → uses
ReplaceClaimAsync.
- Delete →
RemoveClaimAsync. After any mutation, force a security-stamp refresh so the user's existing cookie picks up the change on next request (Identity does this automatically for some operations, not all — verify).
- Claims that are managed by the permission system (
permission type) should be read-only and visually distinguished ("Managed by Permissions module — edit there"). This prevents admins from corrupting the permission claims by hand.
Implementation notes
- New endpoints under
modules/Admin/src/SimpleModule.Admin/Endpoints/Admin/:
AdminUserClaimsListEndpoint (GET) — GetClaimsAsync, project to { type, value }[].
AdminUserClaimAddEndpoint (POST) — validates duplicates, calls AddClaimAsync.
AdminUserClaimUpdateEndpoint (PUT) — uses ReplaceClaimAsync.
AdminUserClaimDeleteEndpoint (DELETE) — RemoveClaimAsync.
- Authorize all endpoints behind an
Admin.Users.ManageClaims permission (define in Admin.Permissions.cs or wherever Admin permissions live).
- Filter out
permission-type claims from the writable surface — those belong to the Permissions module and must not be edited here.
- Front-end: new
UserClaimsTab.tsx component under Pages/Admin/components/, registered in the user edit page.
- Audit logs: emit
UserClaimChangedEvent on every add/update/remove so the AuditLogs module can pick it up.
- Tests: cover add, replace, remove, duplicate prevention, permission claim is read-only.
Benefits
- Unlocks claim-driven features (multi-tenancy keys, JWT enrichment, third-party data) without code changes.
- Gives admins a way to inspect the actual principal that gets built for a user — huge for diagnosing auth bugs.
- Closes one of the largest remaining gaps between SimpleModule and a "complete" Identity admin surface.
- Costs almost nothing operationally — the table is already there.
Acceptance criteria
Summary
Expose direct user-claims management (
UserManager.GetClaimsAsync/AddClaimAsync/RemoveClaimAsync/ReplaceClaimAsync) through the Admin module, with an "Claims" tab on the user edit page.Why we need this
The framework already has a strong custom permission system (
SimpleModule.Permissions) for authorization, and that should remain the primary mechanism for "can this user do X". But ASP.NET Identity's claim store has other legitimate uses that the permission system was never meant to cover:tenant_id,department,cost_center,feature_tier,region. These don't fitpermission— they're not "is allowed to" but "is".hddomain, OIDCgroups).UsersDbContextalready has theAspNetUserClaimstable (it's part ofIdentityDbContext), but nothing reads or writes it through a UI. This wires up the missing surface.How the user (admin) will use it
On
/admin/users/{id}/edit, alongside the existing Details / Roles / Sessions / Security tabs, add a Claims tab:UserManager.GetClaimsAsync(user)→ table ofType / Valuerows, each with a delete button.Type,Value), validation prevents duplicates of the same(Type, Value)pair.ReplaceClaimAsync.RemoveClaimAsync. After any mutation, force a security-stamp refresh so the user's existing cookie picks up the change on next request (Identity does this automatically for some operations, not all — verify).permissiontype) should be read-only and visually distinguished ("Managed by Permissions module — edit there"). This prevents admins from corrupting the permission claims by hand.Implementation notes
modules/Admin/src/SimpleModule.Admin/Endpoints/Admin/:AdminUserClaimsListEndpoint(GET) —GetClaimsAsync, project to{ type, value }[].AdminUserClaimAddEndpoint(POST) — validates duplicates, callsAddClaimAsync.AdminUserClaimUpdateEndpoint(PUT) — usesReplaceClaimAsync.AdminUserClaimDeleteEndpoint(DELETE) —RemoveClaimAsync.Admin.Users.ManageClaimspermission (define inAdmin.Permissions.csor wherever Admin permissions live).permission-type claims from the writable surface — those belong to the Permissions module and must not be edited here.UserClaimsTab.tsxcomponent underPages/Admin/components/, registered in the user edit page.UserClaimChangedEventon every add/update/remove so the AuditLogs module can pick it up.Benefits
Acceptance criteria
UserManagerworks end-to-end.(Type, Value)pairs rejected with a clear error.permission-type claims appear in the list but are read-only.Admin.Users.ManageClaimspermission.