chore: Add scoped JWT strategy for public API (no-changelog)#28333
Draft
phyllis-noester wants to merge 5 commits intomasterfrom
Draft
chore: Add scoped JWT strategy for public API (no-changelog)#28333phyllis-noester wants to merge 5 commits intomasterfrom
phyllis-noester wants to merge 5 commits intomasterfrom
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Contributor
Performance ComparisonComparing current → latest master → 14-day baseline Idle baseline with Instance AI module loaded
docker-stats
Memory consumption baseline with starter plan resources
How to read this table
|
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.
PR Summary
https://linear.app/n8n/issue/IAM-469
What this PR does
Implements
ScopedJwtStrategy— the auth strategy that validates scoped JWTs issued by the token exchange endpoint and authenticates public API requests with them.Scoped JWTs can be presented in either
Authorization: Bearer <token>orx-n8n-api-keyheaders. The strategy identifies them by their issuer (n8n-token-exchange) and is registered intoAuthStrategyRegistrybyTokenExchangeModuleon startup, making it the second strategy afterApiKeyAuthStrategy.Changes
ScopedJwtStrategy(new)Authorization: Bearerandx-n8n-api-keyheadersdecode()+ issuer check before the expensiveverify()call, so non-token-exchange JWTs pass through asnullwithout signature overheadsub→subjectand optionallyact.sub→actorfrom the DBactor ?? subject) — not from the JWT payload — so permission changes take effect immediately without re-issuing tokensreq.userto the acting principal (actor if delegation is present, subject otherwise)req.tokenGrant = { scopes, subject, actor? }ApiKeyAuthStrategy(updated)subject: apiKeyRecord.usertoreq.tokenGrant—TokenGrant.subjectis now requiredx-n8n-api-keyis a JWT with an issuer other thanAPI_KEY_ISSUER, the strategy returnsnull(abstain) instead offalse(fail-fast). Without this, a token-exchange JWT inx-n8n-api-keywould short-circuit the auth chain beforeScopedJwtStrategyrunsTokenExchangeModule(updated)ScopedJwtStrategyintoAuthStrategyRegistryat the end ofinit(), behind the existing feature flag guardKey decisions
Scopes from role, not JWT payload. Scopes are resolved at request time from the acting user's
role.scopesrather than from thescopeclaim embedded in the JWT. This means permission changes take effect immediately and the JWT itself only needs to carry identity claims (sub,act).Actor is optional. If the JWT carries an
actclaim but the actor user ID is not found in the DB (e.g. the account was deleted after the token was issued), authentication continues with the subject acting as principal. Only if the actor is found and disabled does authentication fail. This keeps token-exchange tokens usable across user lifecycle events.ApiKeyAuthStrategyabstains on non-API-key JWTs. The issuer check (decoded.iss !== API_KEY_ISSUER) is placed before the DB lookup so a token-exchange JWT inx-n8n-api-keygetsnull(pass through) rather thanfalse(block). A null decode (non-JWT string) still returnsfalseto preserve existing rejection behaviour for garbage values.Module self-registration (Option B).
ScopedJwtStrategyis registered byTokenExchangeModule.init()rather than in the public API bootstrap. This keeps the token-exchange module self-contained and ensures the strategy is only active when the module is enabled.Review / Merge checklist
Backport to Beta,Backport to Stable, orBackport to v1(if the PR is an urgent fix that needs to be backported)