Skip to content

Identity: admin user claims management #175

@antosubash

Description

@antosubash

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:

  1. Tab loads → backend calls UserManager.GetClaimsAsync(user) → table of Type / Value rows, each with a delete button.
  2. "Add claim" form: two text inputs (Type, Value), validation prevents duplicates of the same (Type, Value) pair.
  3. Edit existing claim → uses ReplaceClaimAsync.
  4. 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).
  5. 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

  • Claims tab visible on user edit page (admins only).
  • CRUD on user claims via UserManager works end-to-end.
  • Duplicate (Type, Value) pairs rejected with a clear error.
  • permission-type claims appear in the list but are read-only.
  • All mutations gated behind Admin.Users.ManageClaims permission.
  • Audit event fires per change.
  • Integration tests cover the full CRUD + permission claim guard.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions