Clean up JWT API surface and eliminate redundant subject claims#217
Merged
Conversation
Agent-Logs-Url: https://github.com/amalgamated-tools/goauth/sessions/c6c7f8f6-f889-4ab9-9d31-b07d7860a5f7 Co-authored-by: veverkap <22348+veverkap@users.noreply.github.com>
Agent-Logs-Url: https://github.com/amalgamated-tools/goauth/sessions/c6c7f8f6-f889-4ab9-9d31-b07d7860a5f7 Co-authored-by: veverkap <22348+veverkap@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
veverkap
May 7, 2026 21:24
View session
Contributor
There was a problem hiding this comment.
Pull request overview
This PR refines the auth surface area by simplifying the JWT manager API (removing unused context.Context params), consolidating JWT user identity into the single standard sub claim (jwt.RegisteredClaims.Subject), improving error preservation in the OAuth2/OIDC user-resolution race-retry path, and aligning one log field name with the repo’s structured logging conventions.
Changes:
- Removed unused
context.Contextparameters fromJWTManagermethods and updated call sites + tests/docs accordingly. - Removed the redundant custom
Claims.UserIDfield and migrated consumers toclaims.Subject. - Improved OAuth2/OIDC race-retry error handling to preserve underlying datastore failures; standardized one email verification log key to
user_id.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates JWT usage example to the new JWTManager signatures and Subject-based user ID. |
| docs/auth/jwt.md | Updates JWT documentation to reflect the narrower API and Subject-only user identity. |
| auth/jwt.go | Removes ctx params, drops custom Claims.UserID, and uses RegisteredClaims.Subject for user ID. |
| auth/jwt_test.go | Updates tests for new JWTManager signatures and Subject-based assertions. |
| auth/middleware.go | Switches JWT resolution to ValidateToken(token) and returns claims.Subject. |
| auth/middleware_test.go | Updates middleware tests to use the new JWTManager method signatures. |
| auth/rbac_test.go | Updates RBAC tests to use the new CreateToken signature. |
| handler/helpers.go | Updates tokenCreator interface and issuance flow to the new JWTManager signatures. |
| handler/helpers_test.go | Updates tokenCreator test double to match the new interface. |
| handler/magiclink_test.go | Updates magiclink token-creation mock to match the new signature. |
| handler/auth.go | Uses claims.Subject for session revocation on logout. |
| handler/auth_test.go | Updates logout tests to use the new JWTManager signatures. |
| handler/oauth2_common.go | Preserves datastore failures in the race-retry lookup path via wrapped errors. |
| handler/email_verification.go | Renames the log key from userID to user_id for consistency. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
handler/oauth2_common.go:63
- New branch returns a wrapped error when the race-retry FindByEmail call fails with a non-ErrNotFound. There doesn’t appear to be a test covering this branch, so the intended behavior (preserving the datastore failure instead of falling through to the generic "failed to resolve OIDC user") isn’t locked in. Add a unit test that forces FindByEmail to return a non-ErrNotFound error in the race-retry path and assert the returned error wraps the underlying failure.
if u, err := users.FindByEmail(ctx, email); err == nil {
linkSubjectBestEffort(ctx, users, u.ID, subject, "race_retry")
return u, nil
} else if !errors.Is(err, auth.ErrNotFound) {
return nil, fmt.Errorf("look up user by email after email race: %w", err)
}
- Files reviewed: 14/14 changed files
- Comments generated: 1
…ateUser Exercises the non-ErrNotFound failure branches on the race-retry FindByOIDCSubject and FindByEmail calls (lines 55-56 and 61-62 of oauth2_common.go), asserting that the underlying error is preserved via errors.Is.
This was referenced May 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This change tightens a few API/design edges called out in the issue set: the JWT manager exposed unused
context.Contextparameters, JWTs encoded the user ID twice via twosubclaim sources, OAuth2 race-retry fallback hid underlying datastore failures, and one email-verification log key drifted from the repo’s structured logging convention.JWT API surface
context.Contextparameters from:JWTManager.CreateTokenJWTManager.CreateTokenWithSessionJWTManager.ValidateTokenJWT claims model
Claims.UserIDfield and now usejwt.RegisteredClaims.Subjectas the single source of truth for the authenticated user ID.middleware, logout/session handling, tests, docs) to readclaims.Subject.OAuth2/OIDC user resolution
findOrCreateUserrace-retry path instead of collapsing them into a generic"failed to resolve OIDC user"error.ErrNotFoundcases, but operational failures now retain context via wrapped errors.Structured logging consistency
userIDtouser_idto match the rest of the handlers.Greptile Summary
This PR cleans up the
JWTManagerAPI by removing unusedcontext.Contextparameters fromCreateToken,CreateTokenWithSession, andValidateToken, and eliminates a long-standingsubclaim duplication where bothClaims.UserID(withjson:\"sub\"tag) andjwt.RegisteredClaims.Subjectserialized to the same JWT field. It also tightens error propagation in the OAuth2 race-retry path and standardizes a log key in the email verification handler.Claims.UserIDfield — previously the Go JSON embedding rules meant onlyUserID(at depth 1) was marshaled/unmarshaled whileRegisteredClaims.Subjectwas silently ignored. Nowclaims.Subjectis the single authoritative field, and all consumers (middleware, logout, tests, docs) have been updated accordingly.ErrEmailExistsnow propagates wrapped store errors instead of silently returning a generic message, and two focused tests cover both new error paths.email_verification.golog key is renamed fromuserIDtouser_idto match the rest of the codebase.Confidence Score: 5/5
Safe to merge — the JWT wire format is unchanged (sub still maps to RegisteredClaims.Subject), all consumer call sites are updated consistently, and the race-retry error paths now correctly propagate store failures.
The sub-claim fix is backward-compatible: old tokens carry sub in the JSON payload and the new Claims struct parses it into RegisteredClaims.Subject just as before. Context parameters were unused in the JWT methods, so removing them cannot change runtime behavior. The OAuth2 error-propagation change is additive — it surfaces previously swallowed errors and is covered by new tests. No auth logic or token-validation rules were altered.
No files require special attention.
Important Files Changed
Sequence Diagram
sequenceDiagram participant C as Client participant H as Handler (issueTokens) participant J as JWTManager participant M as Middleware (resolveUser) Note over J: CreateToken(userID) — no ctx H->>J: CreateToken(userID) J-->>H: "signedJWT (sub=userID)" Note over J: CreateTokenWithSession(userID, sessID) — no ctx H->>J: CreateTokenWithSession(userID, sessID) J-->>H: "signedJWT (sub=userID, jti=sessID)" C->>M: Request + Bearer token Note over M: ValidateToken(token) — no ctx M->>J: ValidateToken(tokenString) J-->>M: "*Claims{RegisteredClaims}" Note over M: userID = claims.Subject Note over M: sessID = claims.IDReviews (2): Last reviewed commit: "test(handler): add unit tests for race-r..." | Re-trigger Greptile