Token exchange endpoint now issues n8n JWTs

External identities can now be swapped for n8n-internal tokens through a standards-compliant OAuth endpoint, enabling secure service-to-service delegation and impersonation workflows.
External systems and services can now exchange their own authentication tokens for n8n-native JWTs. A new endpoint at POST /auth/oauth/token implements the OAuth 2.0 Token Exchange standard (RFC 8693), accepting external JWTs as subject_token and optionally actor_token to establish delegation chains.
When a token exchange succeeds, n8n issues an HS256-signed JWT containing the subject's identity, scopes, and optional actor context. The lifetime of issued tokens is capped by configuration (default: 900 seconds) and cannot exceed the original token's expiry.
Audit events capture every exchange attempt—successful or failed—routing subject, issuer, actor, and client IP to the log stream for compliance and debugging.
This is part of n8n's broader identity and access management initiative. The endpoint is gated behind the N8N_TOKEN_EXCHANGE_ENABLED feature flag and returns 501 when disabled.
View Original GitHub Description
Summary
Implements the POST /auth/oauth/token token exchange endpoint (RFC 8693) and the logic to issue n8n-internal scoped JWTs from it.
Endpoint: POST /auth/oauth/token
- Accepts
application/x-www-form-urlencodedwithgrant_type=urn:ietf:params:oauth:grant-type:token-exchange - Gated behind
N8N_TOKEN_EXCHANGE_ENABLED=true(returns501when disabled) - Rate-limited to 20 requests/minute per IP
JWT issuance:
- Decodes and validates the incoming
subject_token(and optionalactor_token) againstExternalTokenClaimsSchema - Computes expiry as
min(subject.exp, actor.exp?, now + N8N_TOKEN_EXCHANGE_MAX_TOKEN_TTL)(default TTL: 900s) - Issues an HS256 JWT signed with n8n's internal key containing:
{ iss, sub, act?, scope[], resource?, iat, exp, jti } actclaim is only present when anactor_tokenis provided (delegation vs impersonation)- Returns an RFC 8693-compliant response:
{ access_token, token_type: "Bearer", expires_in, issued_token_type }
Audit events: token-exchange-succeeded and token-exchange-failed are emitted with subject, issuer, actor, and client IP — routed to log-streaming via the existing relay infrastructure.
New config:
N8N_TOKEN_EXCHANGE_ENABLED(default:false) — enable the endpointN8N_TOKEN_EXCHANGE_MAX_TOKEN_TTL(default:900) — ceiling for issued token lifetime in seconds
How to test
# Enable the endpoint
export N8N_TOKEN_EXCHANGE_ENABLED=true
# POST a token exchange request
curl -X POST http://localhost:5678/auth/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
-d "subject_token=<valid-external-jwt>"
# Decode the returned access_token to verify claims:
# { iss: "n8n", sub, iat, exp, jti } should be present
# act claim present only when actor_token was supplied
Related Linear tickets, Github issues, and Community forum posts
https://linear.app/n8n/issue/IAM-464
Review / Merge checklist
- 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, orBackport to v1(if the PR is an urgent fix that needs to be backported)