Zum Hauptinhalt springen

Authentifizierung

Die REST-API authentifiziert jede Anfrage mit einem OAuth2-/OIDC-Access-Token (einem Bearer-JWT), das von unserem Identity-Provider (Keycloak) ausgestellt wird. Es gibt keinen separaten API-Schlüssel-Mechanismus — Server-zu-Server-Integrationen verwenden den OAuth2-Flow Client Credentials, um dieselbe Art von Token zu erhalten.

Ein Access-Token verwenden​

Fügen Sie das Token bei jeder Anfrage im Authorization-Header ein:

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
https://api.tourfold.com/api/v2/areas

Anfragen ohne gültiges Token erhalten 401 Unauthorized — Payload und Retry-Regeln finden Sie unter Fehlerantworten.

Access-Token beziehen​

Token werden von unserem Keycloak-Realm ausgestellt. Die Endpoints des Realms sind hier auffindbar:

https://auth.tourfold.com/auth/realms/tourfold/.well-known/openid-configuration

Authorization Code Flow (empfohlen für nutzerorientierte Apps)​

  1. Leiten Sie den Benutzer an den Authorization-Endpoint weiter:
https://auth.tourfold.com/auth/realms/tourfold/protocol/openid-connect/auth
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=YOUR_REDIRECT_URI
&scope=openid profile email
&state=YOUR_STATE_VALUE
  1. Tauschen Sie den Authorization Code gegen Token ein:
curl -X POST https://auth.tourfold.com/auth/realms/tourfold/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=YOUR_REDIRECT_URI"

Antwort:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 300,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Client Credentials Flow (Server-zu-Server)​

Verwenden Sie für Hintergrund-Jobs und automatisierte Systeme einen vertraulichen Client und den Client-Credentials-Grant, um ein Token direkt zu beziehen — ohne Benutzerinteraktion:

curl -X POST https://auth.tourfold.com/auth/realms/tourfold/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"

Scopes und Autorisierung​

Fordern Sie bei der Authentifizierung die üblichen OIDC-Scopes an:

  • openid — für OIDC-Konformität erforderlich
  • profile — Zugriff auf grundlegende Profil-Claims
  • email — Zugriff auf den E-Mail-Claim

Der Zugriff auf einzelne Ressourcen wird über die Rollen und Grants des Workspace des Aufrufers geregelt, nicht über OAuth-Scopes. Das Token identifiziert, wer Sie sind und in welchem Workspace Sie agieren; was Sie lesen oder schreiben dürfen, entscheiden die Rollenzuweisungen dieser Identität. Ein Token, das gültig ist, aber den erforderlichen Grant nicht besitzt, erhält 403 Forbidden; der fehlende Grant steht in data.required_grants, sofern er statisch bekannt ist.

Token-Verwaltung​

Ein Access-Token erneuern​

Access-Token sind kurzlebig. Verwenden Sie das Refresh-Token, um ein neues zu erhalten:

curl -X POST https://auth.tourfold.com/auth/realms/tourfold/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN"

Eine Session widerrufen​

curl -X POST https://auth.tourfold.com/auth/realms/tourfold/protocol/openid-connect/logout \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "refresh_token=YOUR_REFRESH_TOKEN"

Sicherheits-Best-Practices​

  • Bewahren Sie Token und Client-Secrets sicher auf — in Umgebungsvariablen oder einem Secrets-Manager, niemals in der Versionsverwaltung.
  • Behandeln Sie den Ablauf — implementieren Sie eine Refresh-Token-Logik für langlaufende Anwendungen.
  • Nur HTTPS verwenden — senden Sie Token niemals über unverschlüsselte Verbindungen.
  • Fordern Sie nur die Scopes an, die Sie benötigen.
  • Verwenden Sie einen eigenen Client je Umgebung — getrennte Clients/Anmeldedaten für Entwicklung, Staging und Produktion.

Enterprise: dedizierter Realm​

Standardmäßig authentifizieren sich alle Workspaces gegen den gemeinsamen tourfold-Realm. Enterprise-Workspaces können mit einem eigenen dedizierten Keycloak-Realm bereitgestellt werden — etwa um einen eigenen Identity-Provider / SSO, eigene Passwort- und Session-Richtlinien oder eine isolierte User-Federation einzubringen. Wird ein dedizierter Realm verwendet, wird das Segment tourfold in den obigen Endpoints durch den Realm-Namen des Workspace ersetzt. Kontaktieren Sie den Support, um einen dedizierten Realm einzurichten.

Fehlerantworten​

Fehler bei Authentifizierung und Autorisierung sind RFC-9457-Problemdokumente und werden — wie jeder andere API-Fehler — als application/problem+json ausgeliefert. Zwei Statuscodes sind hier relevant:

  • 401 Unauthorized — die Anfrage enthielt keine verwendbare Identität. Behoben wird das durch das Beziehen oder Erneuern von Anmeldedaten.
  • 403 Forbidden — die Anfrage ist authentifiziert, aber diese Identität darf die Aktion nicht ausführen. Andere Anmeldedaten helfen nicht; andere Grants schon.

Beide enthalten ein maschinenlesbares data.reason, damit ein Client das weitere Vorgehen ohne Auswertung des menschenlesbaren detail-Texts entscheiden kann.

Das unten beschriebene Autorisierungs-Vokabular gilt für access-denied und spezifischere Autorisierungsablehnungen. Ein typspezifischer 403-Fehler kann einen eigenen Ursachenwert definieren; tenant-disabled ist das bestehende, unten erläuterte Beispiel.

hinweis

Behandeln Sie data als optional — greifen Sie niemals ohne Existenzprüfung auf data.reason zu. Bei 401 ist data.reason immer vorhanden. Bei 403 ist es vorhanden, sobald die Ursache bekannt ist, und data.required_grants nur dann, wenn die Ablehnung auf Grants beruht und die erforderlichen Grants statisch bekannt sind (eine als eigene Regel implementierte Prüfung kann keine benennen).

401 — nicht authentifiziert​

Wird zurückgegeben, wenn der Authorization-Header fehlt oder das Token abgelaufen, fehlerhaft oder von einem nicht vertrauenswürdigen Issuer ausgestellt ist:

{
"type": "https://problems.tourfold.com/authentication-required",
"title": "Authentication required",
"status": 401,
"detail": "Valid authentication credentials are required",
"data": {
"reason": "token_expired"
}
}

Der Problemtyp type bleibt für alle drei Ursachen authentication-required — die Ursache steht in data.reason, nicht in einer separaten Typ-URI:

data.reasonUrsacheWas der Client tun soll
token_missingEs wurden überhaupt keine Anmeldedaten übermittelt.Über einen der oben beschriebenen Flows authentifizieren. Eine unveränderte Wiederholung scheitert identisch.
token_expiredEin wohlgeformtes Token, dessen Gültigkeit abgelaufen ist.Access-Token erneuern und die Anfrage einmal wiederholen. Nur diese Ursache rechtfertigt einen automatischen Retry.
token_invalidFehlerhaftes Token, ungültige Signatur oder nicht vertrauenswürdiger Issuer.Keinen blinden Retry ausführen — dasselbe Token scheitert weiterhin. Token verwerfen und neu authentifizieren.

Jede 401-Antwort enthält zusätzlich einen WWW-Authenticate-Header:

  • Ein Token wurde übermittelt, aber abgelehnt → der Challenge enthält die Fehlerparameter nach RFC 6750, zum Beispiel WWW-Authenticate: Bearer error="invalid_token", error_description="...".
  • Es wurde kein Token gesendet → ein einfaches WWW-Authenticate: Bearer.

Ein weiterer Problemtyp führt zu 401: https://problems.tourfold.com/login-failed („Login failed“). Er deckt einen abgelehnten Login mit Anmeldedaten ab, nicht ein fehlendes oder unbrauchbares Bearer-Token.

Token-Erneuerung implementieren​

Das Ursachen-Vokabular lässt sich direkt auf eine Refresh-Logik abbilden:

  1. 401 mit data.reason = token_expired → Refresh-Token gegen ein neues Access-Token tauschen und die ursprüngliche Anfrage einmal wiederholen.
  2. 401 mit data.reason = token_missing → es sind keine Anmeldedaten in der API angekommen. Client korrigieren und authentifizieren.
  3. 401 mit data.reason = token_invalid → abbrechen. Das zwischengespeicherte Token verwerfen und neu authentifizieren; eine Wiederholung scheitert garantiert und verbraucht nur Rate-Limit-Budget.
  4. Der Refresh-Aufruf selbst scheitert → das Refresh-Token ist abgelaufen oder widerrufen; den vollständigen Authorization-Flow erneut durchlaufen.

403 — authentifiziert, aber nicht berechtigt​

Wird zurückgegeben, wenn der Aufrufer bekannt ist, die Aktion aber nicht ausführen darf:

{
"type": "https://problems.tourfold.com/access-denied",
"title": "Access denied",
"status": 403,
"detail": "You don't have permission to perform this action",
"data": {
"reason": "missing_grant",
"required_grants": ["users:update"]
}
}

Bei access-denied und spezifischeren Autorisierungsablehnungen ist data.reason einer der folgenden Werte:

data.reasonBedeutung
missing_grantDem Aufrufer fehlt einer der für die Operation erforderlichen Grants.
not_ownerDer Aufrufer ist nicht der Eigentümer der Zielressource.
not_authorDer Aufrufer hat die Zielressource nicht erstellt.
not_memberDer Aufrufer ist kein Mitglied der Gruppe, zu der die Ressource gehört.
plan_restrictedDer Plan bzw. die Feature-Flags des Workspace enthalten diese Funktion nicht.
tenant_scopeDie Ressource gehört zu einem anderen Workspace als dem des Aufrufers.
resource_lockedDie Ressource befindet sich in einem Zustand, der die Aktion für alle verbietet.

Ein deaktivierter Workspace wird als typspezifisches Problem https://problems.tourfold.com/tenant-disabled mit data.reason = TENANT_INACTIVE gemeldet. Prüfen Sie zuerst type, bevor Sie typspezifische data-Felder auswerten; behandeln Sie TENANT_INACTIVE nicht als zusätzlichen Wert der geschlossenen Liste für Autorisierungsablehnungen.

data.required_grants listet die Grants auf, die die Prüfung erfüllt hätten. Das Feld ist nur vorhanden, wenn die Ablehnung grant-basiert ist und die erforderlichen Grants statisch bekannt sind — Clients müssen es deshalb als optional behandeln. Grant-Werte sind durch Doppelpunkte getrennt, zum Beispiel users:read, users:update, tenant:ownership:update, custom-objects:definitions:update, webhooks:create.

Eine 403-Antwort zu wiederholen hilft für sich genommen nie: entweder benötigt der Aufrufer eine Rolle mit dem fehlenden Grant, oder die Aktion ist für diese Ressource nicht verfügbar.

Die interne Admin-Control-Plane (admin-v1) ist bewusst schlanker gehalten. Sie authentifiziert Aufrufer und liefert 401 in genau der oben beschriebenen Form, besitzt aber kein Autorisierungsmodell auf Grant-Ebene und erzeugt daher keine grant-basierten 403-Antworten.

Die meisten Workspace-bezogenen Operationen durchlaufen zusätzlich Prüfungen des Tenant-Zustands. Ein deaktivierter Workspace liefert 403 tenant-disabled; ein aufgrund der Abrechnung gesperrter Workspace liefert 402 tenant-billing-locked. Billing-Portal-, Zahlungsmethoden- und Billing-Recheck-Operationen bleiben erreichbar, damit der Workspace die Sperre beheben kann. Dabei handelt es sich um Fehler des Workspace-Zustands, nicht um Fehler der Token-Authentifizierung.

Auf type und Status verzweigen, nicht auf detail​

Ein Feature kann anstelle des generischen access-denied einen spezifischeren, namensraumbezogenen Problemtyp zurückgeben — zum Beispiel https://problems.tourfold.com/comments/cannot-edit-others. Der status bleibt 403 und data.reason enthält weiterhin die grobe maschinenlesbare Ursache; Code, der auf Status plus data.reason prüft, funktioniert also weiter, während ein Client, der den genauen Fall benötigt, auf die spezifischere type-URI matchen kann.

type-URIs und HTTP-Statuscodes sind stabiler Vertragsbestandteil. title und detail sind menschenlesbar und können jederzeit umformuliert oder lokalisiert werden — verzweigen Sie niemals darauf.

Das vollständige problem+json-Format finden Sie unter Fehler, den kompletten Typkatalog in der Fehlertypen-Referenz.

Rate-Limiting​

Die Authentifizierung unterliegt dem Rate-Limiting. Einzelheiten finden Sie unter Rate-Limiting.

Nächste Schritte​