임베딩 파트너를 위한 토큰 교환
n8n v2.29토큰 교환은 환경 변수 플래그 뒤에 있는 미리보기 기능입니다. OAuth 2.0 토큰 교환(RFC 8693)을 사용하면 임베딩 파트너가 임베드된 n8n 인스턴스 내에서 사용자를 인증하고 사용자를 대신하여 작업을 수행할 수 있습니다.
기능 제공 범위
- Enterprise 플랜에서 사용할 수 있습니다.
N8N_ENV_FEAT_TOKEN_EXCHANGE환경 변수를true로 설정하면 활성화됩니다. 셀프 호스팅 인스턴스는 직접 설정할 수 있습니다. Cloud에서는 n8n 지원팀에 문의하여 요청하세요.- 서명된 JWT를 발급하는 외부 ID 공급자(IdP)나 백엔드를 운영하는 임베딩 파트너를 위한 기능입니다.
미리보기 기능
토큰 교환은 환경 변수 플래그 뒤에 있는 미리보기 기능입니다. 환경 변수, 엔드포인트 경로, JWT 클레임 계약은 이 기능이 일반 공급(GA) 단계에 도달하기 전까지 변경될 수 있습니다. n8n 버전을 고정하고 업그레이드할 때마다 통합을 다시 테스트하세요.
OAuth 2.0 토큰 교환(RFC 8693)을 사용하면 임베딩 파트너가 임베드된 n8n 인스턴스 내에서 사용자를 인증하고 사용자를 대신하여 작업을 수행할 수 있습니다. 토큰 교환은 두 가지 사용 사례를 지원합니다.
- Iframe SSO: 외부 JWT를 n8n 세션 쿠키로 교환하여, n8n을 iframe에 임베드할 때 사용자가 매끄럽게 로그인하도록 합니다.
- 위임된 API 접근: 외부 JWT를 n8n 접근 토큰으로 교환하여 사용자를 대신해 n8n API를 호출합니다. 예를 들어 워크플로를 트리거하거나 자격 증명을 관리하는 데 사용합니다.
두 흐름 모두 같은 방식으로 시작합니다. 백엔드가 개인 키로 서명한 수명이 짧은 JWT를 발급합니다. n8n은 등록된 공개 키로 이를 검증하고, 사용자를 확인한 다음, 세션 쿠키 또는 접근 토큰을 반환합니다.
시작하기 전에#
다음이 필요합니다.
- 토큰 교환 기능이 활성화된 Enterprise 라이선스.
- 인스턴스에 설정된 미리보기 기능 플래그:
N8N_ENV_FEAT_TOKEN_EXCHANGE=true. - RSA 또는 EC 키 쌍, 또는 IdP가 이미 게시하고 있는 JWKS 엔드포인트. 키 쌍 생성을 참고하세요.
- HTTPS로 제공되는 n8n 인스턴스. 브라우저는 일반 HTTP를 통한
SameSite=None; Secure세션 쿠키를 거부하므로, HTTPS가 없으면 iframe SSO 흐름이 조용히 실패합니다. TLS를 종료하는 프록시 뒤에서 n8n을 실행하는 경우, 프록시가X-Forwarded-Proto헤더를 설정하도록 하세요.
키 쌍 생성#
비대칭 키 쌍이 필요합니다. 백엔드는 개인 키로 JWT를 서명하고, n8n은 공개 키로 이를 검증합니다.
RSA (RS256)
# Generate a 2048-bit RSA private key
openssl genrsa -out private.pem 2048
# Extract the public key
openssl rsa -in private.pem -pubout -out public.pem
EC (ES256)
# Generate an EC private key using P-256
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
# Extract the public key
openssl ec -in private.pem -pubout -out public.pem
private.pem은 백엔드에서 비밀로 유지하세요. public.pem의 내용은 n8n의 신뢰할 수 있는 키 구성에 등록합니다.
대부분의 OAuth 2.0 및 OIDC 공급자가 그렇듯, IdP가 이미 JWKS 엔드포인트를 게시하고 있다면 키 생성 과정을 건너뛰고 대신 n8n이 JWKS URL을 가리키도록 할 수 있습니다. JWKS 키 소스를 참고하세요.
환경 변수#
모든 설정에 필요한 변수#
# Enable the preview module
N8N_ENV_FEAT_TOKEN_EXCHANGE=true
# Register your public key(s) - see Configure trusted keys
N8N_TOKEN_EXCHANGE_TRUSTED_KEYS='[{ ... }]'
흐름별 토글#
두 흐름은 서로 독립적입니다. 사용하는 엔드포인트만 활성화하세요.
# Iframe SSO: enable the embed login endpoint (POST or GET /rest/auth/embed)
N8N_EMBED_LOGIN_ENABLED=true
# Delegated API access: enable the token exchange endpoint (POST /rest/auth/oauth/token)
N8N_TOKEN_EXCHANGE_ENABLED=true
임베드 로그인 엔드포인트는 N8N_TOKEN_EXCHANGE_ENABLED를 필요로 하지 않으며, 토큰 교환 엔드포인트는 N8N_EMBED_LOGIN_ENABLED를 필요로 하지 않습니다.
예를 들어 크거나 여러 줄로 이루어진 JSON의 경우, 신뢰할 수 있는 키를 파일에 저장하고 N8N_TOKEN_EXCHANGE_TRUSTED_KEYS_FILE=/path/to/trusted-keys.json을 설정하세요.
선택적 조정 설정#
다음 설정에는 합리적인 기본값이 있으므로 보통 변경할 필요가 없습니다.
| 변수 | 기본값 | 설명 |
|---|---|---|
N8N_TOKEN_EXCHANGE_MAX_TOKEN_TTL |
900 (15분) |
발급된 n8n 토큰의 최대 수명(초). 실제 만료 시간은 이 값, 주체(subject) 토큰의 남은 수명, 행위자(actor) 토큰의 남은 수명(있는 경우) 중 최소값입니다. |
N8N_TOKEN_EXCHANGE_KEY_REFRESH_INTERVAL_SECONDS |
300 (5분) |
엔드포인트가 캐시 수명을 제공하지 않을 때 JWKS 키를 새로 고치는 대체 간격입니다. 모든 인스턴스는 시작 시 키를 새로 고치며, 다중 메인(multi-main) 배포에서는 리더 인스턴스만 주기적으로 새로 고칩니다. |
N8N_TOKEN_EXCHANGE_JTI_CLEANUP_INTERVAL_SECONDS |
60 (1분) |
n8n이 만료된 재전송 방지 레코드를 정리하는 주기입니다. |
N8N_TOKEN_EXCHANGE_JTI_CLEANUP_BATCH_SIZE |
1000 |
정리 작업당 삭제되는 만료된 레코드의 최대 개수입니다. |
N8N_TOKEN_EXCHANGE_EMBED_LOGIN_PER_MINUTE |
20 |
임베드 로그인 엔드포인트의 속도 제한(IP당 분당 요청 수)입니다. |
N8N_TOKEN_EXCHANGE_TOKEN_EXCHANGE_PER_MINUTE |
20 |
토큰 교환 엔드포인트의 속도 제한(IP당 분당 요청 수)입니다. |
신뢰할 수 있는 키 구성#
N8N_TOKEN_EXCHANGE_TRUSTED_KEYS 환경 변수는 신뢰할 수 있는 키 소스의 JSON 배열을 받습니다. 각 항목은 n8n이 IdP의 JWT를 검증하는 방법을 알려줍니다.
소스 유형은 두 가지입니다: static(인라인 공개 키)과 jwks(원격 JWKS 엔드포인트). 같은 배열에 두 유형을 함께 사용할 수 있습니다.
정적(static) 키 소스#
직접 생성한 키 쌍이 있고 공개 키를 직접 임베드하려는 경우 사용합니다.
{
"type": "static",
"kid": "my-key-1",
"algorithms": ["RS256"],
"key": "-----BEGIN PUBLIC KEY-----\nMIIBIjAN...contents-of-public.pem...\n-----END PUBLIC KEY-----",
"issuer": "https://your-backend.example.com",
"expectedAudience": "https://your-n8n.example.com",
"allowedRoles": ["global:member"]
}
| 필드 | 필수 여부 | 설명 |
|---|---|---|
type |
필수 | "static"이어야 합니다. |
kid |
필수 | 키 ID입니다. 수신하는 JWT의 kid 헤더와 일치해야 합니다. |
algorithms |
필수 | 허용된 서명 알고리즘의 배열입니다. 예: ["RS256"]. 지원되는 알고리즘을 참고하세요. |
key |
필수 | PEM으로 인코딩된 공개 키입니다. JSON에서 줄바꿈은 \n을 사용하세요. |
issuer |
필수 | 수신하는 JWT에서 예상되는 iss 클레임입니다. |
expectedAudience |
선택 | 설정하면 JWT의 aud 클레임이 이 값과 일치해야 합니다. 설정하지 않으면 n8n은 대상(audience) 검증을 완전히 건너뜁니다. n8n 기본 URL과 같은 인스턴스별 값을 사용해 프로덕션에서는 항상 설정하세요. |
allowedRoles |
선택 | 설정하면 role 클레임을 통해 이 역할만 할당할 수 있습니다. 통합이 관리자 사용자를 의도적으로 관리하려는 경우에만 global:admin을 포함하세요. 역할 처리를 참고하세요. |
JWKS 키 소스#
IdP가 JWKS 엔드포인트를 게시하는 경우 사용합니다.
{
"type": "jwks",
"url": "https://idp.example.com/.well-known/jwks.json",
"issuer": "https://idp.example.com",
"expectedAudience": "https://your-n8n.example.com",
"allowedRoles": ["global:member"]
}
| 필드 | 필수 여부 | 설명 |
|---|---|---|
type |
필수 | "jwks"여야 합니다. |
url |
필수 | JWKS 엔드포인트의 URL입니다. |
issuer |
필수 | 수신하는 JWT에서 예상되는 iss 클레임입니다. |
expectedAudience |
선택 | 설정하면 JWT의 aud 클레임이 이 값과 일치해야 합니다. 설정하지 않으면 n8n은 대상 검증을 완전히 건너뜁니다. n8n 기본 URL과 같은 인스턴스별 값을 사용해 프로덕션에서는 항상 설정하세요. |
allowedRoles |
선택 | 설정하면 role 클레임을 통해 이 역할만 할당할 수 있습니다. 역할 처리를 참고하세요. |
cacheTtlSeconds |
선택 | JWKS 엔드포인트가 Cache-Control: max-age 헤더를 보내지 않을 때 사용하는 대체 캐시 지속 시간입니다. 헤더가 있으면 그 값이 우선합니다. 기본값은 3600초입니다. 실제 적용 값은 60초에서 86400초 사이로 제한됩니다. |
전체 예시#
[
{
"type": "static",
"kid": "my-static-key-1",
"algorithms": ["RS256"],
"key": "-----BEGIN PUBLIC KEY-----\nMIIBIjAN...your-key-here...\n-----END PUBLIC KEY-----",
"issuer": "https://your-backend.example.com",
"expectedAudience": "https://your-n8n.example.com",
"allowedRoles": ["global:member"]
},
{
"type": "jwks",
"url": "https://idp.example.com/.well-known/jwks.json",
"issuer": "https://idp.example.com",
"expectedAudience": "https://your-n8n.example.com"
}
]
지원되는 알고리즘#
n8n은 비대칭 알고리즘만 허용합니다. HMAC과 none은 제외됩니다.
| 계열 | 알고리즘 |
|---|---|
| RSA | RS256, RS384, RS512 |
| RSA-PSS | PS256, PS384, PS512 |
| 타원 곡선(Elliptic Curve) | ES256, ES384, ES512 |
| Edwards 곡선 | EdDSA |
정적 키의 경우, 구성에 포함된 알고리즘은 모두 같은 계열에 속해야 하며 키 유형과 일치해야 합니다. JWKS 키의 경우, n8n은 JWK의 alg와 kty 또는 crv 필드로부터 알고리즘을 자동으로 추론합니다.
필수 및 선택 JWT 클레임#
필수 클레임#
n8n이 IdP 토큰을 수락하려면 다음 클레임이 포함되어야 합니다.
| 클레임 | 유형 | 설명 |
|---|---|---|
sub |
문자열 | 주체 식별자입니다. IdP의 고유한 사용자 ID입니다. |
iss |
문자열(URL) | 발급자입니다. 신뢰할 수 있는 키 구성의 issuer와 일치해야 합니다. |
aud |
문자열 또는 문자열 배열 | 대상(audience)입니다. 반드시 존재해야 합니다. n8n은 신뢰할 수 있는 키 소스에 expectedAudience가 구성된 경우에만 값을 검증합니다. |
iat |
숫자 | 발급 시각(Unix epoch 초)입니다. |
exp |
숫자 | 만료 시각(Unix epoch 초)입니다. |
jti |
문자열 | 고유한 토큰 ID입니다. n8n은 각 값을 한 번만 허용합니다(재전송 방지). |
선택 클레임#
| 클레임 | 유형 | 설명 |
|---|---|---|
email |
문자열(유효한 이메일) | 기존 n8n 사용자와 매칭하는 데 사용되는 사용자의 이메일입니다. n8n이 아직 알지 못하는 사용자의 첫 로그인인 JIT 프로비저닝에 필요합니다. 모든 사용자가 이미 존재한다고 확신하지 않는 한 항상 보내세요. |
given_name |
문자열 | n8n 사용자 프로필에 동기화되는 이름(First name)입니다. |
family_name |
문자열 | n8n 사용자 프로필에 동기화되는 성(Last name)입니다. |
role |
문자열 | 할당할 n8n 역할입니다. 예: global:member 또는 global:admin. 사용자 프로비저닝을 참고하세요. |
nbf |
숫자 | Not-before 시각입니다. |
Iframe SSO 흐름#
n8n을 iframe에 임베드할 때 이 흐름을 사용하세요. n8n은 세션 쿠키로 사용자를 투명하게 로그인시킵니다.
소스 코드 보기
sequenceDiagram
participant Browser as User browser
participant Backend as Your backend
participant n8n as n8n instance
Browser->>Backend: 1. Load iframe
Backend->>Backend: 2. Mint JWT (private key)
Backend-->>Browser: 3. Return auto-submitting form (token)
Browser->>n8n: 4. POST /rest/auth/embed (token in body)
n8n->>n8n: 5. Verify JWT
n8n->>n8n: 6. Resolve user
n8n->>n8n: 7. Set session cookie
n8n-->>Browser: 8. Redirect (with session cookie)
Browser->>n8n: 9. User is logged in, loads n8n UI</code></pre></details></div>
1단계: 백엔드에서 JWT 발급#
백엔드는 개인 키로 서명한, 수명이 짧은 JWT를 생성합니다.
const fs = require('fs');
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto');
const privateKey = fs.readFileSync('private.pem', 'utf8');
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
sub: 'user-id-in-your-system', // unique user identifier
iss: 'https://your-backend.example.com', // must match trusted key config
aud: 'https://your-n8n.example.com', // must match expectedAudience (if set)
iat: now,
exp: now + 30, // short-lived: 30 seconds
jti: randomUUID(), // unique per request
email: 'user@example.com', // required for first-time users
given_name: 'Jane', // optional
family_name: 'Doe', // optional
role: 'global:member', // optional
},
privateKey,
{ algorithm: 'RS256', header: { kid: 'my-key-1' } }
);
Warning
최대 60초 수명
임베드 로그인 흐름의 경우, JWT 수명(exp - iat)은 60초를 초과할 수 없습니다. n8n은 이를 서버 측에서 강제합니다.
2단계: 임베드 엔드포인트로 토큰 전송#
토큰을 폼 제출 형태로 POST /rest/auth/embed에 전송하세요. iframe의 src는 자동 제출되는 폼을 반환하는 백엔드 페이지를 가리키도록 설정하세요.
<form method="POST" action="https://your-n8n.example.com/rest/auth/embed">
<input type="hidden" name="token" value="<jwt>">
<input type="hidden" name="redirectTo" value="/workflow/abc123">
</form>
<script>document.forms[0].submit();</script>
선택적인 redirectTo 필드는 /로 시작하는 상대 경로만 허용합니다. 절대 URL이나 그 밖의 값이 오면 n8n은 /로 되돌아갑니다.
GET /rest/auth/embed?token=<jwt>&redirectTo=/workflow/abc123 형태의 변형도 존재합니다. POST 폼을 사용하는 것을 권장합니다. GET을 사용하면 토큰이 서버와 프록시 로그, 브라우저 히스토리, 그리고 경우에 따라 Referer 헤더에 노출됩니다. 부득이하게 GET을 사용해야 한다면 인프라에서 로그의 쿼리 문자열을 반드시 삭제하도록 하세요.
3단계: n8n이 검증하고 세션을 발급#
n8n은 JWT 서명을 검증하고, 사용자를 확인하거나 프로비저닝한 뒤(사용자 프로비저닝 참고), 안전한 세션 쿠키(SameSite=None; Secure)를 설정하고 지정된 경로로 리다이렉트합니다.
서드파티 쿠키 제한#
n8n이 제품과 다른 등록 가능 도메인(registrable domain)에서 실행되는 경우, 세션 쿠키는 서드파티 쿠키가 됩니다. Safari의 지능형 추적 방지(Intelligent Tracking Prevention)와 Chrome의 서드파티 쿠키 제한과 같은 브라우저 프라이버시 기능이 쿠키를 차단하거나 분리하여 iframe 로그인을 깨뜨릴 수 있습니다. 이를 피하려면 제품 사이트의 서브도메인(예: automation.your-product.example.com)에서 n8n을 호스팅하세요.
위임된 API 접근 흐름#
백엔드가 사용자를 대신하여 n8n API를 호출해야 할 때(예: 워크플로를 트리거하거나 프로그래밍 방식으로 자격 증명을 관리할 때) 이 흐름을 사용하세요.
이 흐름은 위임을 위한 선택적 행위자(actor) 토큰을 지원합니다. 서비스 계정이나 관리자와 같은 행위자가 실제 사용자인 주체(subject)를 대신하여 작업을 수행합니다. 이를 통해 감사 귀속(audit attribution)이 가능해지므로, n8n은 누가 작업을 수행했는지와 누구를 대신했는지를 모두 기록합니다.
행위자 토큰을 제공하면, n8n은 행위자의 신원과 권한으로 API 호출을 인가합니다. n8n은 감사 귀속을 위해서만 주체를 기록하며, 주체는 토큰이 수행할 수 있는 작업을 제한하지 않습니다. 이는 주체가 대개 유효한 주체(effective principal)로 남는 RFC 8693과 다른 점입니다. 통합에 필요한 권한만 부여하는 역할을 가진 행위자를 사용하세요.
Mermaid 다이어그램 (11줄)소스 코드 보기
sequenceDiagram
participant Backend as Your backend
participant n8n as n8n instance
Backend->>Backend: 1. Mint subject JWT (+ optional actor JWT)
Backend->>n8n: 2. POST /rest/auth/oauth/token (token-exchange grant)
n8n->>n8n: 3. Verify JWTs
n8n->>n8n: 4. Resolve users
n8n->>n8n: 5. Issue n8n access token
n8n-->>Backend: 6. access_token, token_type, expires_in
Backend->>n8n: 7. Call n8n APIs with Bearer token</code></pre></details></div>
1단계: 백엔드에서 JWT 발급#
최종 사용자를 나타내는 주체 토큰을 생성합니다. 임베드 흐름의 60초 제한은 여기에는 적용되지 않습니다. 발급된 n8n 접근 토큰은 주체 토큰의 남은 수명, 행위자 토큰의 남은 수명(있는 경우), N8N_TOKEN_EXCHANGE_MAX_TOKEN_TTL(기본값 15분) 중 최솟값이 지나면 만료됩니다.
const now = Math.floor(Date.now() / 1000);
const subjectToken = jwt.sign(
{
sub: 'end-user-id',
iss: 'https://your-backend.example.com',
aud: 'https://your-n8n.example.com',
iat: now,
exp: now + 900, // bounds the issued token's lifetime
jti: randomUUID(),
email: 'user@example.com',
role: 'global:member',
},
privateKey,
{ algorithm: 'RS256', header: { kid: 'my-key-1' } }
);
위임을 위해서는 작업을 수행하는 서비스나 관리자를 나타내는 행위자 토큰도 발급하세요. 행위자의 n8n 역할이 발급되는 토큰의 권한을 결정하므로, 가능한 한 낮은 권한으로 유지하세요.
const actorToken = jwt.sign(
{
sub: 'service-account-id',
iss: 'https://your-backend.example.com',
aud: 'https://your-n8n.example.com',
iat: now,
exp: now + 900,
jti: randomUUID(),
email: 'service@example.com',
},
privateKey,
{ algorithm: 'RS256', header: { kid: 'my-key-1' } }
);
2단계: n8n 접근 토큰으로 교환#
curl -X POST https://your-n8n.example.com/rest/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=<subject-jwt>" \
-d "actor_token=<actor-jwt>"
요청 필드(application/x-www-form-urlencoded):
필드
필수 여부
설명
grant_type
필수
urn:ietf:params:oauth:grant-type:token-exchange여야 합니다.
subject_token
필수
최종 사용자를 나타내는 JWT입니다.
subject_token_type
선택
토큰 유형 식별자입니다. n8n은 이 필드를 수락하지만 무시합니다. RFC 8693은 이 필드를 요구하므로, 스펙을 준수하는 클라이언트 라이브러리를 사용한다면 urn:ietf:params:oauth:token-type:jwt를 보내세요.
actor_token
선택
행위자를 나타내는 JWT입니다(위임용).
actor_token_type
선택
행위자 토큰 유형 식별자입니다. subject_token_type과 마찬가지로 수락되지만 무시됩니다.
requested_token_type
선택
요청된 토큰 유형 식별자입니다. 수락되지만 무시됩니다. n8n은 항상 접근 토큰을 발급합니다.
scope
선택
요청된 범위(scope)입니다(최대 1024자). 발급된 토큰과 감사 이벤트에 기록되지만 강제되지는 않습니다.
audience
선택
의도된 대상(audience)입니다(최대 1024자). 수락되지만 무시됩니다.
resource
선택
공백으로 구분된 대상 리소스 URI입니다(최대 2048자). 발급된 토큰과 감사 이벤트에 기록되지만 강제되지는 않습니다.
Warning
범위(scope)는 강제되지 않습니다
발급된 접근 토큰은 작업을 수행하는 사용자의 전체 권한(행위자 토큰을 보낸 경우 행위자의 권한, 그렇지 않으면 주체의 권한)을 가집니다. n8n은 감사를 위해서만 scope와 resource를 기록하며 토큰을 이에 맞게 제한하지 않습니다. 통합이 할 수 있는 작업을 제한하려면 대신 저권한 행위자와 allowedRoles 설정을 사용하세요.
성공 응답(200 OK):
{
"access_token": "<n8n-jwt>",
"token_type": "Bearer",
"expires_in": 900,
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token"
}
3단계: 접근 토큰 사용#
이후의 n8n API 호출에 토큰을 포함하세요.
curl https://your-n8n.example.com/api/v1/workflows \
-H "Authorization: Bearer <access-token>"
토큰은 expires_in 초가 지나면 만료됩니다. 만료되면 새 토큰을 요청하세요. 각 jti는 일회용이므로 원본 외부 JWT를 재사용하지 마세요.
사용자 프로비저닝#
토큰을 교환할 때, n8n은 다음 순서로 외부 신원을 n8n 사용자로 확인합니다.
- 알려진 신원: n8n은 신원 저장소에서
sub 클레임을 조회합니다. 이전 교환에서 이미 이 sub가 n8n 사용자와 연결되어 있다면, n8n은 해당 사용자를 반환합니다.
- 이메일 대체(fallback):
sub를 알 수 없지만 JWT에 email 클레임이 포함되어 있다면, n8n은 해당 이메일을 가진 기존 사용자를 검색합니다. 찾으면 n8n은 이후부터 외부 신원을 그 사용자와 연결합니다.
- 즉시(Just-in-time, JIT) 프로비저닝: 둘 다 일치하지 않으면, n8n은 자동으로 새 사용자를 생성합니다. 이를 위해서는 JWT에
email 클레임이 포함되어야 합니다. n8n은 비밀번호 로그인이 비활성화된 상태로 새 사용자를 생성하므로, 이 사용자는 토큰 교환을 통해서만 인증할 수 있습니다.
역할 처리#
role 클레임을 포함하면, IdP가 해당 사용자의 전역 역할에 대한 단일 진실 공급원(Single Source Of Truth, SSOT)이 됩니다. n8n은 교환할 때마다 이를 적용하여 n8n UI에서 할당된 역할을 덮어씁니다. IdP에서 역할을 관리하지 않는 한 이 클레임을 생략하세요.
시나리오
동작
신규 사용자, role 클레임 없음
global:member로 할당됩니다.
신규 사용자, role 클레임 존재
클레임에 명시된 역할이 할당됩니다. 역할을 인식할 수 없거나 global:owner이거나 allowedRoles에 없으면 교환이 실패합니다.
기존 사용자, role 클레임 없음
역할이 변경되지 않습니다.
기존 사용자, 유효한 role 클레임
다르면 역할이 갱신됩니다. 역할이 allowedRoles에 없으면 교환이 실패합니다.
기존 사용자, 인식할 수 없거나 global:owner인 role 클레임
서버 측 경고와 함께 클레임이 무시됩니다. 역할은 변경되지 않은 채 로그인이 진행됩니다.
global:owner인 기존 사용자
역할 동기화가 완전히 건너뛰어집니다. 소유자(owner) 역할은 토큰 교환으로 변경할 수 없습니다.
프로필 동기화#
n8n은 로그인할 때마다 given_name과 family_name 클레임을 사용자 프로필에 동기화합니다. 저장된 값과 다를 때만 변경을 적용하며, 32자를 초과하는 값은 잘라냅니다.
보안 고려 사항#
수명이 짧은 토큰#
- 임베드 흐름의 외부 JWT는 수명이 최대 60초(
exp - iat <= 60)여야 합니다.
- 토큰 교환 흐름의 경우, 발급된 n8n 토큰 만료 시간은 주체 토큰의 남은 수명, 행위자 토큰의 남은 수명(있는 경우),
N8N_TOKEN_EXCHANGE_MAX_TOKEN_TTL(기본값 900초) 중 최솟값입니다.
- 토큰 교환 흐름에서, 발급될 토큰의 계산된 만료 시간이 5초 미만으로 남아 있으면 n8n은 요청을 거부합니다.
재전송 방지#
모든 외부 JWT에는 고유한 jti(JWT ID) 클레임이 포함되어야 합니다. n8n은 각 jti를 기록하고, 이미 사용된 jti를 가진 토큰은 모두 거부합니다. n8n은 만료된 jti 레코드를 자동으로 정리합니다.
비대칭 서명만 허용#
n8n은 비대칭 알고리즘(RSA, EC, EdDSA)만 허용합니다. HS256과 같은 HMAC 알고리즘과 none은 설계상 거부합니다. 이를 통해 n8n이 서명 비밀 키에 접근할 필요가 없도록 보장합니다.
대상(audience) 제한#
모든 신뢰할 수 있는 키 소스에 expectedAudience를 항상 설정하세요. 설정하지 않으면 n8n은 대상 검증을 건너뛰며, 구성된 발급자로부터 발급된 유효한 토큰이라면 무엇이든 교환될 수 있습니다. 여기에는 IdP가 다른 서비스나 다른 n8n 인스턴스를 위해 발급한 토큰도 포함됩니다. n8n 기본 URL과 같은 인스턴스별 값을 사용하세요.
역할 제약#
신뢰할 수 있는 키 소스의 allowedRoles 필드는 토큰 교환을 통해 할당할 수 있는 역할을 제한합니다. 예를 들어 임베딩 통합이 global:member 사용자만 프로비저닝하도록 제한하는 등 최소 권한 원칙을 강제하는 데 사용하세요. OAuth의 scope와 resource 요청 필드는 권한을 제한하지 않습니다. 2단계: n8n 접근 토큰으로 교환을 참고하세요.
프레이밍 제한#
임베드 세션 쿠키는 SameSite=None을 사용하므로, 브라우저는 어떤 서드파티 iframe 컨텍스트에서도 이를 전송합니다. n8n 인스턴스를 프레임에 넣을 수 있는 사이트를 제한하세요. 리버스 프록시를 구성하여 제품의 출처(origin)만 나열하는 Content-Security-Policy: frame-ancestors 헤더를 보내도록 하세요. 프레이밍 보호를 통째로 비활성화하지 마세요.
키 순환 및 유출#
다운타임 없이 키를 순환하려면, N8N_TOKEN_EXCHANGE_TRUSTED_KEYS에 새 kid를 가진 항목을 추가하고, 백엔드가 새 키로 서명하도록 전환한 다음, 기존 항목을 제거하세요. 개인 키가 유출된 경우, 즉시 해당 항목을 제거하세요. 이렇게 하면 새로운 교환은 중단되지만, 이미 발급된 n8n 토큰과 세션은 짧은 토큰 수명에 의해 제한되며 만료될 때까지 유효한 상태로 유지됩니다.
감사 귀속#
n8n은 모든 토큰 교환 활동에 대해 감사 이벤트를 발생시킵니다.
이벤트
발생 시점
n8n.audit.token-exchange.succeeded
토큰 교환 성공.
n8n.audit.token-exchange.failed
토큰 교환 실패(실패 사유 포함).
n8n.audit.token-exchange.embed-login
임베드 로그인 성공.
n8n.audit.token-exchange.embed-login-failed
임베드 로그인 실패(실패 사유 포함).
n8n.audit.token-exchange.user-provisioned
JIT 프로비저닝을 통한 신규 사용자 생성.
n8n.audit.token-exchange.identity-linked
기존 사용자가 새 외부 신원과 연결됨.
n8n.audit.token-exchange.role-updated
토큰 교환을 통해 사용자 역할이 변경됨.
행위자 토큰 흐름을 사용하면, n8n은 주체(누구를 대신했는지)와 행위자(누가 작업을 수행했는지)를 모두 기록하여 감사 로그에서 완전한 귀속을 제공합니다.
설정 검증#
일회성 JWT를 발급하고 교환해 보세요. 1단계 예시를 mint-token.js로 저장하고, 끝에 console.log(token);을 추가한 다음 실행하세요.
node mint-token.js
그런 다음 토큰 교환 엔드포인트를 호출하세요.
curl -X POST https://your-n8n.example.com/rest/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=<jwt>"
access_token이 포함된 200 응답은 키, 클레임, 구성이 올바르게 동작한다는 것을 확인해 줍니다. 임베드 흐름의 경우, 브라우저에서 새 토큰을 POST /rest/auth/embed에 제출하여 n8n UI가 로드되는지 확인하세요.
문제 해결#
증상
가능성 있는 원인
두 엔드포인트 모두 404
미리보기 플래그 N8N_ENV_FEAT_TOKEN_EXCHANGE가 true가 아니거나, 라이선스에 토큰 교환 기능이 포함되어 있지 않습니다. n8n은 모듈 자체를 로드하지 않습니다.
501 - Token exchange is not enabled on this instance
N8N_TOKEN_EXCHANGE_ENABLED가 true가 아닙니다.
501 - Embed login is not enabled on this instance
N8N_EMBED_LOGIN_ENABLED가 true가 아닙니다.
400 - unsupported_grant_type
grant_type 필드가 없거나 정확히 urn:ietf:params:oauth:grant-type:token-exchange가 아닙니다.
설명이 Token exchange failed인 400 - invalid_grant
토큰 교환 엔드포인트에서의 모든 토큰 검증 실패: 잘못된 서명, 알 수 없는 kid, 누락된 kid 헤더, 재전송된 jti, 만료되었거나 만료 임박한 토큰, 대상 불일치, 또는 허용되지 않은 역할입니다. n8n은 설계상 일반적인 설명을 반환합니다. 구체적인 원인은 n8n 서버 로그를 확인하세요.
설명이 Token claims validation failed인 400 - invalid_request
토큰 교환 엔드포인트에서: JWT에 필수 클레임(sub, iss, aud, iat, exp, jti)이 없거나, 클레임의 타입이 잘못되었습니다.
임베드 엔드포인트에서의 401
응답에 구체적인 실패 메시지가 포함됩니다. 예: Token has already been used(재전송된 jti), Token lifetime exceeds maximum allowed(임베드 토큰은 exp - iat <= 60초여야 함), 또는 Token header missing kid.
임베드 엔드포인트에서의 500
JWT에 필수 클레임이 없거나 클레임의 타입이 잘못되었습니다.
iframe에 원시(raw) JSON 오류가 표시됨
임베드 로그인 실패는 오류 리다이렉트 없이 JSON 응답을 반환합니다. 위 표에서 메시지를 확인하세요.
임베드 로그인 후 세션 쿠키가 설정되지 않음
인스턴스가 HTTPS로 제공되지 않거나, TLS를 종료하는 프록시가 X-Forwarded-Proto를 전달하지 않습니다. 브라우저는 일반 HTTP를 통한 SameSite=None; Secure 쿠키를 거부합니다. 서드파티 쿠키 제한도 쿠키를 차단할 수 있습니다. 서드파티 쿠키 제한을 참고하세요.
첫 로그인 시 사용자가 생성되지 않음
JWT에 JIT 프로비저닝에 필요한 email 클레임이 없습니다.
역할이 적용되지 않음
기존 사용자의 경우, n8n은 인식할 수 없거나 global:owner인 역할 클레임을 서버 측 경고와 함께 무시합니다. 유효한 역할이라도 allowedRoles에 없으면 교환이 거부됩니다. 역할 처리를 참고하세요.
관련 리소스#
- OEM 배포 개요: n8n의 인터페이스를 제품 내에 임베드하고 노출합니다.
- SSO 설정: SAML 또는 OIDC를 통한 조직 전체 단일 로그인(SSO)입니다.
- HTTP Request 자격 증명: OAuth2 사용하기: 범용 OAuth 2.0 자격 증명을 설정합니다.
- OAuth 2.0 토큰 교환(RFC 8693): 토큰 교환 사양입니다.