feat(auth): AuthenticatedUser accepts OIDC at+jwt alongside legacy HS256 (BUNYIP-55) #69
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
psa-systems/bunyip!69
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/bunyip-55-authenticated-user-accept-atjwt"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Until now, bunyip's
AuthenticatedUser/AdminUser/MemberUser/OptionalUserextractors only validated legacy HS256 access tokens viaJwtService::verify_access_token. The OIDC EdDSA at+jwt access tokens thatbunyip-oidc's/oauth2/tokenendpoint mints itself were rejected by the same extractors with a 401, meaning external relying parties (and bunyip's own SPAs that authenticate through OIDC) could not call first-party bunyip endpoints with the token bunyip issued them. This blew up concretely on mokosh-clients' calendar page, where every page load logged a 401 onGET /v1/auth/memberships.The extractor now peeks at the JWT header's
typclaim and routes accordingly:typ == "at+jwt"goes through anAtJwtVerifiertrait object pulled from app data, anything else continues through the legacy HS256 verifier. Both paths produce the sameAccessTokenClaimsshape downstream handlers consume, soAdminUserandMemberUserpredicates work identically on either token shape. The HS256 latency path is unchanged when the token is HS256-shaped (no extra crypto, no DB lookup).The trait
AtJwtVerifieris defined inbunyip-domain(the leaf crate) so the dependency direction staysbunyip-api -> bunyip-oidc -> bunyip-domain.bunyip-oidc::OidcProviderimplements it: verify EdDSA signature + typ + iss + exp against its own JWKS, then resolvesubto aUserrow, then map to the legacyAccessTokenClaimsshape via a newAccessTokenClaims::from_atjwt_and_userconstructor. When OIDC is disabled (noOIDC_ISSUERenv),bunyip-api/main.rsregisters aDisabledAtJwtVerifierstub that always rejects, so an at+jwt presented to a non-OIDC bunyip deployment fails withUnauthorized(identical to the pre-BUNYIP-55 behaviour) while HS256 tokens still work.Refactored
verify_at_jwt_get_subout ofbunyip-oidc/src/handlers/oidc.rsinto a pub methodOidcProvider::verify_at_jwt_claimsreturning the fullAtClaims; the userinfo handler now calls that method, and the newAtJwtVerifierimpl reuses it. Addedvalidate_aud = false(RFC 9068 does not require anaudcheck for inbound bunyip-API calls; per-handler authorization stays the handler's job).Tests in
crates/bunyip-domain/src/middleware/auth.rs:token_is_atjwt_recognises_rfc9068_typ/_rejects_legacy_jwt_typ/_rejects_malformed_token: routing predicate pinned.verify_either_routes_atjwt_to_verifier_when_present: at+jwt hits the AtJwtVerifier exactly once.verify_either_rejects_atjwt_when_no_verifier_registered: at+jwt with no verifier returns Unauthorized (the no-OIDC fallback case).verify_either_routes_legacy_jwt_to_jwt_service: non-at+jwt routes to JwtService; missing service surfaces the pre-BUNYIP-55 internal-error message.disabled_atjwt_verifier_always_rejects: stub behaviour pinned.access_token_claims_from_atjwt_and_user_maps_fields: User -> AccessTokenClaims mapping pinned (including thelocked_price_id -> price_idrename and trial_ends_at timestamp coercion).Once this lands, the mokosh-clients
use_memberships_loadersynthesis workaround (introduced in mokosh-apps PRfix/logout-profile-memberships-noise) can be reverted to a real network call againstGET /v1/auth/memberships.Notes:
cargo test --workspaceshows one pre-existing failure (config::tests::test_config_defaults) that reproduces onmainindependently of this change. Filed/tracked separately.