feat(guards): accept the auth API's access token as a bearer credential - #162
Merged
Merged
Conversation
requireAuth and getSeamlessUser read req.cookies and nothing else, so a native client, which has no cookie jar and holds the auth API's own tokens, was rejected on every request to an adopter's routes. requireAuth takes an optional authServerUrl + audience pair. With both set it also accepts Authorization: Bearer <access token>, verified against the auth API's JWKS and required to carry typ "access", so a sign-in flow's ephemeral token is refused even though the same key signs it. The cookie wins when both are present, and leaving the pair out keeps the guard cookie-only. getSeamlessUser resolves a bearer session too. The router options already carry the audience, so a request with no cookie but a valid bearer token is verified the same way and forwarded to GET /users/me, while a request with neither, or a token that fails verification, returns null without an upstream call. Core exports verifyAccessToken, extractBearerToken, authenticateBearer and authenticateRequest. The JWKS memo moves to a shared module so both verifiers share one instance per auth server. Fastify gains direct guard tests, which it had only through the parity suite. Refs #147.
This was referenced Sep 13, 2026
jose skips the claim check for an empty expected value, so a blank audience would have verified against any audience. verifyAccessToken returns null for an empty issuer or audience, and requireAuth refuses the pair at setup.
Contributor
Author
|
Security review (adapter branch, both commits plus the transport PR stacked on top) found no exploitable issue. Two below-threshold notes, both addressed or documented:
|
13 tasks
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.
Summary
requireAuthandgetSeamlessUserreadreq.cookiesand nothing else, so a native client, which has no cookie jar and holds the auth API's own tokens, was rejected on every request to an adopter's routes. This is the first PR of the mobile track (fells-code/seamless-templates#40) and closes #147.requireAuth(Express and Fastify) takes an optionalauthServerUrl+audiencepair. With both configured it also acceptsAuthorization: Bearer <access token>, verified against the auth API's JWKS (issuer, audience, expiry) and required to carrytyp: "access", so a sign-in flow's ephemeral token is refused even though the same key signs it. The cookie wins when both are present. Leaving the pair out keeps the guard cookie-only, which is what every earlier version did. Half of the pair throws at setup.getSeamlessUserresolves a bearer session too. The router options already carryauthServerUrlandaudience, so no new option is needed there. A request with no access cookie but a valid bearer token is verified the same way and forwarded toGET /users/me; a request with neither, or a token that fails verification, returnsnullwithout an upstream call.req.user.emailandreq.user.phoneare unset (the access token does not carry them);getSeamlessUserhydrates the profile.verifyAccessToken,extractBearerToken,authenticateBearer,authenticateRequest. The JWKS memo thatverifySignedAuthResponseused moves tojwks.tsso both verifiers share one instance per auth server.requireAuth,requireRoleandgetSeamlessUser, which until now were covered only through the plugin parity suite.Security notes
iss,aud,expandtyp === "access", and fails closed on any error including a JWKS fetch failure.kids cannot be used to hammer the auth API.Test plan
pnpm -r test: core 285, express 180, fastify 81 passingcore/tests/verifyAccessToken.test.js,core/tests/authenticateBearer.test.js,core/tests/getSeamlessUser.bearer.test.js,express/tests/requireAuth.bearer.test.js,fastify/tests/guards.test.jsFollow-ups
/authproxy (the client-facing half; issue to follow), then theseamless-templatesAPI starters passauthServerUrl/audienceintorequireAuth.