Skip to content

chore: Add scoped JWT strategy for public API (no-changelog)#28333

Draft
phyllis-noester wants to merge 5 commits intomasterfrom
iam-469-scoped-jwt-strategy
Draft

chore: Add scoped JWT strategy for public API (no-changelog)#28333
phyllis-noester wants to merge 5 commits intomasterfrom
iam-469-scoped-jwt-strategy

Conversation

@phyllis-noester
Copy link
Copy Markdown
Contributor

@phyllis-noester phyllis-noester commented Apr 10, 2026

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> or x-n8n-api-key headers. The strategy identifies them by their issuer (n8n-token-exchange) and is registered into AuthStrategyRegistry by TokenExchangeModule on startup, making it the second strategy after ApiKeyAuthStrategy.

Changes

ScopedJwtStrategy (new)

  • Extracts tokens from both Authorization: Bearer and x-n8n-api-key headers
  • Performs a cheap decode() + issuer check before the expensive verify() call, so non-token-exchange JWTs pass through as null without signature overhead
  • Resolves subsubject and optionally act.subactor from the DB
  • Loads scopes from the acting user's role (actor ?? subject) — not from the JWT payload — so permission changes take effect immediately without re-issuing tokens
  • Sets req.user to the acting principal (actor if delegation is present, subject otherwise)
  • Sets req.tokenGrant = { scopes, subject, actor? }

ApiKeyAuthStrategy (updated)

  • Added subject: apiKeyRecord.user to req.tokenGrantTokenGrant.subject is now required
  • Added early-exit before the DB lookup: if the value in x-n8n-api-key is a JWT with an issuer other than API_KEY_ISSUER, the strategy returns null (abstain) instead of false (fail-fast). Without this, a token-exchange JWT in x-n8n-api-key would short-circuit the auth chain before ScopedJwtStrategy runs

TokenExchangeModule (updated)

  • Registers ScopedJwtStrategy into AuthStrategyRegistry at the end of init(), behind the existing feature flag guard

Key decisions

Scopes from role, not JWT payload. Scopes are resolved at request time from the acting user's role.scopes rather than from the scope claim 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 act claim 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.

ApiKeyAuthStrategy abstains on non-API-key JWTs. The issuer check (decoded.iss !== API_KEY_ISSUER) is placed before the DB lookup so a token-exchange JWT in x-n8n-api-key gets null (pass through) rather than false (block). A null decode (non-JWT string) still returns false to preserve existing rejection behaviour for garbage values.

Module self-registration (Option B). ScopedJwtStrategy is registered by TokenExchangeModule.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

  • I have seen this code, I have run this code, and I take responsibility for this code.
  • PR title and summary are descriptive. (conventions)
  • Docs updated or follow-up ticket created.
  • Tests included.
  • PR Labeled with Backport to Beta, Backport to Stable, or Backport to v1 (if the PR is an urgent fix that needs to be backported)

@n8n-assistant n8n-assistant bot added core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team labels Apr 10, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 93.54839% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...les/token-exchange/services/scoped-jwt.strategy.ts 95.45% 1 Missing and 1 partial ⚠️
packages/cli/src/public-api/index.ts 75.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

Performance Comparison

Comparing currentlatest master14-day baseline

Idle baseline with Instance AI module loaded

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
instance-ai-heap-used-baseline 185.91 MB 186.51 MB 186.46 MB (< 3 samples) -0.3% -0.3%
instance-ai-rss-baseline 339.73 MB 394.55 MB 369.15 MB (< 3 samples) -13.9% -8.0%

docker-stats

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
docker-image-size-n8n 1269.76 MB 1269.76 MB 1269.76 MB (σ 0.00) +0.0% +0.0%
docker-image-size-runners 386.00 MB 386.00 MB 387.50 MB (σ 3.00) +0.0% -0.4%

Memory consumption baseline with starter plan resources

Metric Current Latest Master Baseline (avg) vs Master vs Baseline Status
memory-heap-used-baseline 114.35 MB 114.53 MB 113.09 MB (σ 1.15) -0.2% +1.1% ⚠️
memory-rss-baseline 283.11 MB 287.07 MB 281.78 MB (σ 34.50) -1.4% +0.5%
How to read this table
  • Current: This PR's value (or latest master if PR perf tests haven't run)
  • Latest Master: Most recent nightly master measurement
  • Baseline: Rolling 14-day average from master
  • vs Master: PR impact (current vs latest master)
  • vs Baseline: Drift from baseline (current vs rolling avg)
  • Status: ✅ within 1σ | ⚠️ 1-2σ | 🔴 >2σ regression

@phyllis-noester phyllis-noester requested review from a team, BGZStephen, afitzek, cstuncsik and guillaumejacquart and removed request for a team April 10, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Enhancement outside /nodes-base and /editor-ui n8n team Authored by the n8n team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant