Token Exchange (RFC 8693)
Das Problem: Ein Token passt nicht für alle
In einer monolithischen Anwendung reicht ein einzelnes Zugriffstoken aus: Der Benutzer authentifiziert sich, erhält ein Token und verwendet es für jeden API-Aufruf. Aber moderne Architekturen sind nicht monolithisch. Eine typische Anfrage könnte mehrere Dienste durchlaufen:
Benutzer → Frontend → API-Gateway → Bestelldienst → Zahlungsdienst → BenachrichtigungsdienstJeder Dienst in dieser Kette hat unterschiedliche Vertrauensniveaus, unterschiedliche Scopes und unterschiedliche Zielgruppen. Das gleiche Token überall zu verwenden, schafft Probleme:
| Problem | Beschreibung |
|---|---|
| Überprivilegierte Tokens | Das Frontend-Token hat read:users write:orders manage:payments, aber der Zahlungsdienst braucht nur process:payments. Wenn der Zahlungsdienst kompromittiert wird, hat der Angreifer Zugriff auf alle Scopes. |
| Falsche Zielgruppe | Ein für frontend-app ausgestelltes Token sollte nicht von payment-service akzeptiert werden. Ohne Zielgruppen-Einschränkung ist jeder Dienst, der das Token akzeptiert, ein gültiges Ziel. |
| Impersonierung | Ein Admin muss das Konto eines Benutzers debuggen, indem er als dieser Benutzer agiert. Es gibt keine Standardmethode, einen anderen Benutzer zu „werden”, ohne seine Anmeldedaten zu kennen. |
| Delegation | Dienst A muss Dienst B im Namen des Benutzers aufrufen, aber Dienst B muss sowohl den ursprünglichen Benutzer als auch die Identität des aufrufenden Dienstes kennen. |
OAuth 2.0 Token Exchange (RFC 8693) bietet ein standardisiertes Protokoll zum Austausch eines Sicherheitstokens gegen ein anderes, mit präziser Kontrolle über das Subjekt, die Zielgruppe und den Scope des resultierenden Tokens.
Was Token Exchange tut
Token Exchange definiert einen neuen Grant-Typ am Token-Endpunkt des Autorisierungsservers. Ein Client sendet ein vorhandenes Token (den subject_token) und optional einen actor_token und erhält dafür ein neues Token mit anderen Eigenschaften zurück.
Die Schlüsseloperationen, die es ermöglicht:
- Impersonierung: Admin erhält ein Token mit
subauf einen anderen Benutzer gesetzt - Delegation: Dienst erhält ein Token, das die ursprüngliche Benutzeridentität beibehält, aber den Dienst als Akteur aufzeichnet
- Zielgruppen-Einschränkung: Dienst erhält ein Token, das auf eine bestimmte nachgelagerte API beschränkt ist
- Scope-Reduzierung: Dienst erhält ein Token mit weniger Berechtigungen als das Original
Funktionsweise von Token Exchange
Die Token-Exchange-Anfrage
Eine Token-Exchange-Anfrage ist ein POST an den Standard-Token-Endpunkt mit grant_type=urn:ietf:params:oauth:grant-type:token-exchange:
POST /api/auth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=eyJhbGciOiJSUzI1NiJ9...
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=payment-service
&scope=process:payments
&actor_token=eyJhbGciOiJSUzI1NiJ9...
&actor_token_type=urn:ietf:params:oauth:token-type:access_tokenAnfrageparameter
| Parameter | Erforderlich | Beschreibung |
|---|---|---|
grant_type | Ja | Immer urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | Ja | Das auszutauschende Token — repräsentiert die Identität der Partei, in deren Namen die Anfrage gestellt wird |
subject_token_type | Ja | Der Typ des Subjekt-Tokens (siehe Token-Typen unten) |
requested_token_type | Optional | Der gewünschte Typ für das Ausgabe-Token. Standard: access_token. |
audience | Optional | Der Zieldienst oder die API, die das neue Token verwenden wird |
scope | Optional | Die angeforderten Scopes für das neue Token. Muss eine Teilmenge der Scopes des Subjekt-Tokens sein. |
actor_token | Optional | Das Token der Partei, die den Austausch durchführt (der „Akteur”) |
actor_token_type | Bedingt | Erforderlich, wenn actor_token vorhanden ist |
Token-Typen
RFC 8693 definiert standardisierte URNs für Token-Typen:
| Token-Typ-URN | Beschreibung |
|---|---|
urn:ietf:params:oauth:token-type:access_token | OAuth 2.0-Zugriffstoken |
urn:ietf:params:oauth:token-type:refresh_token | OAuth 2.0-Refresh-Token |
urn:ietf:params:oauth:token-type:id_token | OpenID Connect-ID-Token |
urn:ietf:params:oauth:token-type:jwt | Generisches JWT |
Die Antwort
Ein erfolgreicher Token-Exchange gibt ein neues Token zurück:
{
"access_token": "eyJhbGciOiJSUzI1NiJ9...",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 1800,
"scope": "process:payments"
}Der issued_token_type bestätigt, welche Art von Token tatsächlich ausgestellt wurde (es kann sich von requested_token_type unterscheiden, wenn der Server Richtlinien angewendet hat).
Impersonierung vs Delegation
Token Exchange unterstützt zwei grundlegend unterschiedliche Muster: Impersonierung und Delegation. Den Unterschied zu verstehen ist entscheidend für den Aufbau sicherer Systeme.
Impersonierung
Bei der Impersonierung wird der sub-Claim des ausgetauschten Tokens auf den Zielbenutzer gesetzt. Der nachgelagerte Dienst sieht Anfragen „von” dem impers onierten Benutzer. Der ursprüngliche Akteur wird im act-Claim für Prüfzwecke aufgezeichnet, aber der Dienst behandelt die Anfrage so, als käme sie vom Zielbenutzer.
Admin (sub: admin-456) tauscht Token für Benutzer (sub: user-123)
→ Neues Token: sub=user-123, act.sub=admin-456
→ Zahlungsdienst sieht: „Anfrage von user-123 (impersoniert durch admin-456)"Anwendungsfall: Ein Admin muss einen Fehler reproduzieren, der nur das Konto eines bestimmten Benutzers betrifft. Er impersoniert den Benutzer, um dieselben Daten und dasselbe Verhalten zu sehen.
POST /api/auth/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<user-123-token>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=<admin-456-token>
&actor_token_type=urn:ietf:params:oauth:token-type:access_token
&requested_token_type=urn:ietf:params:oauth:token-type:access_tokenDas resultierende JWT:
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "frontend-app",
"exp": 1739882700,
"act": {
"sub": "admin-456"
}
}Delegation
Bei der Delegation bleibt der sub-Claim des ausgetauschten Tokens der ursprüngliche Benutzer. Der Akteur wird im act-Claim aufgezeichnet, aber das Token repräsentiert immer noch die Anfrage des ursprünglichen Benutzers, jetzt vermittelt durch einen Dienst.
Benutzer (sub: user-123) authentifiziert sich → Frontend ruft Bestelldienst auf
→ Bestelldienst tauscht Token, um Zahlungsdienst aufzurufen
→ Neues Token: sub=user-123, act.sub=order-service
→ Zahlungsdienst sieht: „Anfrage von user-123, delegiert über order-service"Anwendungsfall: Dienst-zu-Dienst-Aufrufe in einer Microservice-Architektur, bei der jeder Dienst nachgelagerte Dienste im Namen des Benutzers aufrufen muss, wobei der nachgelagerte Dienst sowohl wissen muss, wer der Benutzer ist, als auch welcher Dienst aufruft.
Das resultierende JWT:
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "payment-service",
"exp": 1739882700,
"scope": "process:payments",
"act": {
"sub": "order-service"
}
}Wesentliche Unterschiede
| Dimension | Impersonierung | Delegation |
|---|---|---|
sub im neuen Token | Zielbenutzer | Ursprünglicher Benutzer (unverändert) |
act-Claim | Ursprünglicher Admin/Akteur | Zwischendienst |
| Nachgelagerter Dienst sieht | „Anfrage vom Zielbenutzer” | „Anfrage vom ursprünglichen Benutzer via Dienst” |
| Erforderliche Berechtigung | impersonate:users | delegate:tokens |
| Risikoniveau | Hoch (vollständige Identitätsannahme) | Mittel (Scope kann reduziert werden) |
| Prüfpfad | act zeichnet auf, wer impersoniert hat | act zeichnet auf, welcher Dienst delegiert hat |
| Typischer Akteur | Admin-Benutzer | Backend-Dienst |
Impersonierung ist eine privilegierte Operation. In Auris können nur Clients mit der impersonate:users-Berechtigung Impersonierungs-Token-Exchanges durchführen. Diese Berechtigung sollte auf Admin-Anwendungen beschränkt und niemals benutzerorientiertern Clients gewährt werden.
Der act-Claim: Delegationsketten
Der act-(Akteur-)Claim ist ein JSON-Objekt, das im JWT eingebettet ist und aufzeichnet, wer den Token-Exchange durchgeführt hat. Akteur-Claims können verschachtelt sein, um eine Kette von Delegationen darzustellen:
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "notification-service",
"act": {
"sub": "payment-service",
"act": {
"sub": "order-service",
"act": {
"sub": "api-gateway"
}
}
}
}Dieses Token erzählt eine Geschichte: user-123 hat eine Anfrage über api-gateway gestellt, das an order-service delegiert hat, das an payment-service delegiert hat, das jetzt notification-service aufruft.
Die Kette lesen
Der äußerste act.sub ist der unmittelbare Aufrufer. Jedes verschachtelte act repräsentiert den vorherigen Hop in der Delegationskette. Das sub auf der obersten Ebene ist immer der ursprüngliche Benutzer.
notification-service empfängt das Token und sieht:
- Wer ist der Benutzer? → sub: user-123
- Wer ruft mich auf? → act.sub: payment-service
- Wer hat payment-service aufgerufen? → act.act.sub: order-service
- Wer hat order-service aufgerufen? → act.act.act.sub: api-gatewayKettentiefenbegrenzungen
Auris begrenzt die Delegationskettentiefe, um Confused-Deputy-Angriffe und unbegrenztes Token-Wachstum zu verhindern. Die Standard-Maximalkettentiefe beträgt 5. Wenn ein Token-Exchange diese Tiefe überschreiten würde, wird die Anfrage abgelehnt mit:
{
"error": "invalid_request",
"error_description": "Maximum delegation chain depth exceeded"
}Sicherheitseigenschaften
Token Exchange ist ein leistungsstarker Mechanismus, der sorgfältige Sicherheitskontrollen erfordert.
Berechtigungsprüfungen
Auris erzwingt explizite Berechtigungsprüfungen bei jedem Token-Exchange:
| Operation | Erforderliche Berechtigung | Wer hat sie |
|---|---|---|
| Impersonierung | impersonate:users | Nur Admin-Anwendungen |
| Delegation | delegate:tokens | Backend-Dienste |
| Zielgruppen-Einschränkung | delegate:tokens | Backend-Dienste |
| Scope-Reduzierung | Keine spezielle Berechtigung | Jeder Client kann seinen eigenen Scope reduzieren |
Scope-Reduzierung (Niemals Erweiterung)
Das ausgetauschte Token kann nicht mehr Berechtigungen haben als das Original. Wenn das Subjekt-Token Scopes read:users write:orders hat, kann das ausgetauschte Token read:users (Teilmenge) anfordern, aber nicht read:users delete:users (Obermenge). Der Autorisierungsserver erzwingt dies durch Schnittmengenbildung des angeforderten Scopes mit dem Scope des Subjekt-Tokens.
Prüfpfad
Jeder Token-Exchange wird protokolliert mit:
- Der Identität des Subjekt-Tokens (
sub) - Der Identität des Akteur-Tokens (falls vorhanden)
- Der angeforderten Zielgruppe und dem Scope
- Ob der Exchange eine Impersonierung oder Delegation war
- IP-Adresse und Zeitstempel
Dieser Prüfpfad ist in der Auris-Konsole unter Logs sichtbar und kann über die API abgefragt werden.
Token-Lebensdauerreduzierung
Ausgetauschte Tokens haben immer eine kürzere Lebensdauer als das Original. Wenn das Subjekt-Token in 1 Stunde abläuft, könnte das ausgetauschte Token in 30 Minuten ablaufen. Dies begrenzt das Expositionsfenster, wenn das ausgetauschte Token kompromittiert wird.
Die spezifische Reduzierung ist pro Anwendung konfigurierbar, mit einem Standard von 50% der verbleibenden Lebensdauer des Subjekt-Tokens.
Sicherheitsüberlegungen
Impersonierungsrisiken
Impersonierung ermöglicht eine vollständige Identitätsannahme. Eine Anwendung mit impersonate:users kann als jeder Benutzer im System agieren. Gegenmaßnahmen:
- Berechtigung einschränken:
impersonate:usersnur Admin-Anwendungen gewähren, niemals benutzerorientierenden Apps - MFA verlangen: Admin muss in der aktuellen Sitzung MFA abgeschlossen haben, um Impersonierung durchzuführen
- Protokollieren und warnen: Alle Impersonierungsereignisse lösen Audit-Log-Einträge und (optional) Benachrichtigungen aus
- Zeitbegrenzung: Impersonierte Tokens haben kurze Lebensdauern (Standard 15 Minuten)
Delegationsketten-Angriffe
Eine Delegationskette A → B → C → D bedeutet, dass jeder Dienst in der Kette sukzessiv eingeschränktere Tokens hat. Ein kompromittierter Dienst in der Mitte der Kette kann jedoch:
- Die Token-Claims lesen (einschließlich der
act-Kette) - Sein delegiertes Token verwenden, um nachgelagerte Dienste aufzurufen
- Er kann nicht Dienste außerhalb der
audiencedes Tokens aufrufen - Er kann nicht den Scope über das hinaus erweitern, was ihm delegiert wurde
Token Exchange vs Alternativen
| Ansatz | Standardisiert | Identität erhalten | Prüfpfad | Scope-Kontrolle |
|---|---|---|---|---|
| Token Exchange (RFC 8693) | Ja | Ja (act-Claim) | Vollständige Kette | Pro Exchange |
| API-Gateway-Token-Transformation | Nein | Variiert | Nur Gateway | Gateway-Ebene |
| Benutzerdefinierte Middleware-Header | Nein | Oft verloren | Manuell | Ad-hoc |
| Gemeinsames Dienstkonto | Nein | Nein (Benutzeridentität verloren) | Minimal | Keine |
Token Exchange ist der standardisierte Ansatz. Es bewahrt die vollständige Identitätskette, bietet granulare Scope-Kontrolle und erstellt einen Prüfpfad am Autorisierungsserver.
Auris-Implementierungsdetails
Prisma-Modell
model TokenExchange {
id String @id @default(cuid())
tenantId String
applicationId String
subjectUserId String
actorUserId String?
requestedAudience String?
requestedScope String?
grantedScope String?
exchangeType TokenExchangeType
createdAt DateTime @default(now())
}
enum TokenExchangeType {
IMPERSONATION
DELEGATION
}Konfiguration
Token Exchange wird pro Anwendung in der Auris-Konsole unter Anwendungen > (Anwendung auswählen) > Einstellungen aktiviert:
| Einstellung | Standard | Beschreibung |
|---|---|---|
| Token Exchange aktivieren | false | Haupt-Toggle |
| Impersonierung erlauben | false | Ob diese Anwendung Impersonierungs-Exchanges durchführen kann |
| Delegation erlauben | false | Ob diese Anwendung Delegations-Exchanges durchführen kann |
| Maximale Kettentiefe | 5 | Maximale Verschachtelungstiefe der act-Claims |
| Ausgetauschte Token-Lebensdauer | 50% | Lebensdauer des ausgetauschten Tokens als Prozentsatz der verbleibenden Lebensdauer des Subjekt-Tokens |
API-Endpunkt
Token Exchange verwendet den Standard-Token-Endpunkt:
| Endpunkt | Methode | Grant-Typ |
|---|---|---|
/api/auth/token | POST | urn:ietf:params:oauth:grant-type:token-exchange |
Codebeispiel: Microservice-Delegationskette
Das folgende Beispiel demonstriert eine Drei-Dienste-Delegationskette:
// api-gateway empfängt das Zugriffstoken des Benutzers und muss order-service aufrufen
async function callOrderService(userAccessToken: string) {
// Token des Benutzers gegen eines für order-service austauschen
const exchangeResponse = await fetch('https://auth.example.com/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: userAccessToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
audience: 'order-service',
scope: 'read:orders write:orders',
}),
})
const { access_token: orderServiceToken } = await exchangeResponse.json()
// order-service mit dem delegierten Token aufrufen
const orders = await fetch('https://order-service.internal/api/orders', {
headers: { Authorization: `Bearer ${orderServiceToken}` },
})
return orders.json()
}
// order-service empfängt das delegierte Token und muss payment-service aufrufen
async function processPayment(delegatedToken: string, orderId: string) {
// Weiterer Exchange: Scope auf payment-service reduzieren
const exchangeResponse = await fetch('https://auth.example.com/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: delegatedToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
audience: 'payment-service',
scope: 'process:payments',
}),
})
const { access_token: paymentToken } = await exchangeResponse.json()
// Das payment-service-Token hat jetzt:
// sub: original-benutzer-id
// aud: payment-service
// scope: process:payments
// act: { sub: order-service, act: { sub: api-gateway } }
return fetch('https://payment-service.internal/api/charge', {
method: 'POST',
headers: {
Authorization: `Bearer ${paymentToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ orderId, amount: 49.99 }),
})
}Admin-Impersonierungs-Beispiel
async function impersonateUser(adminToken: string, targetUserId: string) {
const response = await fetch('https://auth.example.com/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token: adminToken,
subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
requested_token_type: 'urn:ietf:params:oauth:token-type:access_token',
audience: 'frontend-app',
}),
})
if (!response.ok) {
const error = await response.json()
if (error.error === 'insufficient_scope') {
throw new Error('Admin hat keine impersonate:users-Berechtigung')
}
throw new Error(`Token Exchange fehlgeschlagen: ${error.error_description}`)
}
const { access_token } = await response.json()
// Dieses Token hat:
// sub: zielbenutzer-id
// act: { sub: admin-benutzer-id }
// Nachgelagerte Dienste sehen Anfragen „von" dem Zielbenutzer
return access_token
}Jeder Token-Exchange — ob Impersonierung oder Delegation — erstellt einen Eintrag im Auris-Audit-Log. Administratoren können Logs nach Exchange-Typ filtern, um alle Impersonierungsaktivitäten zu überprüfen. In hochsicherheitsrelevanten Umgebungen können Impersonierungsereignisse Echtzeit-Benachrichtigungen an das Sicherheitsteam auslösen.
Verwandte Konzepte
- Tokens erklärt — JWT-Struktur, der
act-Claim und Token-Lebensdauern - OAuth 2.0 & OIDC — Das Autorisierungsframework, das Token Exchange erweitert
- FGA / Zanzibar-Modell — Fine-Grained Authorization, das Impersonierungsberechtigungen steuern kann
- DPoP (Proof of Possession) — Sender-eingeschränkte Tokens, die mit Token Exchange kombiniert werden können