Repository navigation
refactor(authn)!: use a single principal and pass all claims through - #301
mushrafmim wants to merge 2 commits into
Conversation
Replace the grant_type-based user/client principal split with one Principal (Subject, ClientID, Roles, Scopes, ExtraClaims). sub and client_id are required on every token; grant_type is ignored. Every claim outside the fixed schema now passes through in ExtraClaims; ClaimSpec, UserClaims/ClientClaims and WithUserClaims/WithClientClaims are removed. UserProfileService is removed: provisioning belongs in application middleware chained after authn. Closes OpenNSW#291 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to The unified principal and middleware changes appear mergeable after normal checks; callers must follow the documented API migration. Pre-merge checks |
|
Summary
Replaces
authn's user/client principal split with a singlePrincipal.email,ouIdandouHandlebecome standard claims, each configurable as required. Every other claim passes through inExtraClaimswith no declaration needed.Before, every token was split into a user or a client principal by its
grant_typeclaim. That claim isn't part of RFC 9068, so IdPs that don't emit it had every token rejected. The split also invited authorization logic that branches on how a token was obtained rather than on roles and scopes. Withinauthn, the only behaviour that depended on it was user provisioning, which isn't authentication.A token is now accepted when its signature,
iss,audandexpcheck out, it carriessubandclient_id, and it carries every standard claim configured as required. The resultingPrincipalis the same however the token was obtained. Anything the application does with it, such as provisioning a user record, moves to application middleware chained afterauthn.Type of Change
Changes Made
authn/token_parser.go: onePrincipalwith these fields:SubjectandClientID, both always required;RolesandScopes;Email,OUIDandOUHandle, trimmed,""when absent;ExtraClaims.Removed
UserPrincipal,ClientPrincipal,PrincipalType,AllowedGrantTypeand thegrant_typedispatch, along with itsunsupported grant typerejection. A standard claim that is configured as required, but missing or blank, rejects the token.authn/config.go,authn/options.go: newConfig.Claims(ClaimsConfig), with oneClaimConfig{Required}entry per standard claim. Code that builds an extractor directly usesWithClaims. RemovedClaimSpec,UserClaims/ClientClaims,WithUserClaims/WithClientClaims, andRolesClaim/WithRolesClaim. Roles are always read fromroles.authn/context.go:AuthContextis nowAuthContext{Principal Principal}, with nil-safe accessorsSubject(),ClientID(),Roles(),Scopes(),Email(),OUID(),OUHandle()andExtraClaims(). It still satisfiesauthz.Principal. RemovedUserContext,ClientContext,Type()and theUserProfileServiceinterface.authn/extra_claims.go,authn/fixed_schema.go:ExtraClaimsholds every claim outside the fixed schema. The fixed schema issub,client_id,scope,roles,email,ouId,ouHandleand the JWT registered claims; case variants of those names are excluded too. String values are trimmed, and null or blank values are dropped.grant_typeis no longer reserved.authn/middleware.go,authn/manager.go: the new signatures areMiddleware(extractor),RequireAuth(extractor)andNewManager(cfg).authn/README.md: rewritten for the new model. It now includes:Tests: updated for the new model. New tests cover:
grant_typebeing ignored, and passing through as an ordinary claim;RequireAuthseeing the validated principal.Testing
Checklist
Related Issues
Closes #291
This departs from the issue's proposed
claims: [{name, required}]list. Onlyemail,ouIdandouHandleare configurable, as typed per-claim entries. Every other claim passes through without being declared; see this comment. The per-claim entries leave room for a claim-name mapping setting later, next torequired.Screenshots/Demo
N/A
Additional Notes
Breaking changes:
NewManager(userProfileSvc, cfg)becomesNewManager(cfg).MiddlewareandRequireAuthlose theirUserProfileServiceargument. Provisioning moves to application middleware chained afterauthn(see the README).authCtx.User/authCtx.Clientbecome the accessors orauthCtx.Principal.authCtx.Type()and the principal-type constants are removed.UserClaims/ClientClaims(userClaims/clientClaimsin yaml) andWithUserClaims/WithClientClaimsare removed. Useclaims/WithClaimsforemail,ouIdandouHandle; every other claim is inExtraClaims.RolesClaim(rolesClaimin yaml) andWithRolesClaimare removed.Behaviour changes the compiler won't catch:
Subject()now returns the JWTsub. For user tokens it used to return the persisted user ID fromUserProfileService; for client tokens it returnedclient_id. Check anything keyed bySubject().subare rejected. Client-credentials tokens without it used to be accepted.emailalso rejects client-credentials tokens that don't carry one.email,ouIdandouHandlecan be required. Move checks for any other claim into application middleware.email,ouIdandouHandleare no longer inExtraClaims, soExtraClaims().String("email")returns"". UseEmail().rolesClaimkey in a yaml config is silently ignored. An IdP emitting roles under another name must map them toroles.AuthContexthas changed.Follow-ups:
storagepinsauthn v0.3.0. When it is bumped,storage/http_handler_test.goneedsUser: &authn.UserContext{ID: ...}changed toPrincipal: authn.Principal{Subject: ...}.client_idfromazp, or roles from a differently-named claim) or EC (ES256) signing keys.🤖 Generated with Claude Code