# Delegierter Zugang mit OAuth 2.1

Nach dieser Seite lässt Ihre Anwendung einen Menschen entscheiden, statt einen Schlüssel zu verlangen: Sie registriert sich selbst, schickt die Person zur Einwilligung, tauscht den Code mit PKCE gegen ein Zugriffstoken `oat_…` und erneuert oder widerruft es. Das Passwort der Person sieht sie dabei nie.

> **Typ:** anleitung
> **Für wen:** Wer ein Programm baut, das auf fremden Rechnern läuft und kein Geheimnis verbergen kann: ein Werkzeug für die Kommandozeile, eine Anwendung auf dem Schreibtisch, ein KI-Assistent.
> **Voraussetzung:** die Basis-Adresse Ihrer Umgebung in `SZENERA_API` und ein Browser, in dem die einwilligende Person angemeldet ist
> **Ort:** Kommandozeile und Browser

## Wann dieser Weg der richtige ist

| Situation | Wahl |
|---|---|
| Ihr eigener Server spricht mit Szenera | ein API-Schlüssel `ak_…` — er gehört der Organisation und hängt an keinem Konto |
| Ihre Anwendung läuft bei fremden Kunden und soll deren Bestand sehen | dieser Weg: Ein Mensch dort willigt ein, und Ihre Anwendung bekommt ein Token |
| Ein KI-Assistent soll in Ihrem Namen arbeiten | dieser Weg, meist über den [MCP-Dienst](/de/api/mcp-service) |

Ein delegierter Zugang trägt immer zwei Seiten: die Anwendung, die handelt, und das Konto, in dessen Auftrag. Zur Laufzeit gilt die Schnittmenge aus beidem. Verliert die Person ein Recht, verliert Ihre Anwendung es im selben Augenblick mit.

## Die Adressen

Ihre Anwendung muss genau **eine** Adresse fest kennen: die Wegbeschreibung. Alles Übrige steht darin, und zwar absolut. Aussteller und Endpunkte nennen künftig die Metadaten von `auth.<ihre-domain>`, wenn Ihre Umgebung diesen Wirt hat. Tragen Sie darum keinen Endpunkt von Hand ein, sondern lesen Sie ihn aus der Beschreibung, die Sie dort abrufen. Die Adressen an der Basis-Adresse der API bleiben bis auf Weiteres erreichbar.

| Route | Zweck |
|---|---|
| `GET /.well-known/oauth-authorization-server` | Wegbeschreibung des Autorisierungs-Servers (RFC 8414) |
| `GET /.well-known/oauth-protected-resource` | Wegbeschreibung der geschützten Ressource (RFC 9728) |
| `POST /oauth/register` | Registrierung eines öffentlichen Clients (RFC 7591) |
| `GET /oauth/authorize` | prüft die Anfrage und führt zur Einwilligungsseite |
| `POST /oauth/token` | Code oder Erneuerungs-Token gegen eine Ausgabe tauschen |
| `POST /oauth/revoke` | ein Token zurücknehmen (RFC 7009) |
| `GET /v1/me/connected-apps` | die verbundenen Anwendungen des eigenen Kontos |
| `DELETE /v1/me/connected-apps/{clientId}` | einer Anwendung alles nehmen, was dieses Konto ihr gab |

Die ersten sechs Adressen tragen **kein** `/v1`: Ihre Pfade schreiben die Standards wörtlich vor, und ein fremder Client sucht dort und nirgends sonst. Was hinter der Einwilligung liegt, steht regulär unter `/v1` — siehe [Basis-URL, /v1 und Stabilität](/de/api/base-url-and-stability).

## Schritte

1. Holen Sie die Wegbeschreibung. Sie nennt die Endpunkte, die unterstützten Verfahren und unter `scopes_supported` jedes Recht, das ein delegierter Zugang tragen kann.

   ```bash
   curl -sS "$SZENERA_API/.well-known/oauth-authorization-server" | jq '{issuer, authorization_endpoint, token_endpoint}'
   ```

2. Registrieren Sie Ihren Client — einmalig, ohne Anmeldung, ohne Geheimnis. Es gibt nur öffentliche Clients: `token_endpoint_auth_method` ist immer `none`.

   ```bash
   curl -sS -X POST "$SZENERA_API/oauth/register" \
     -H "Content-Type: application/json" \
     -d '{ "client_name": "Warenwirtschaft-Brücke", "redirect_uris": ["http://127.0.0.1:8765/callback"] }'
   ```

   ```json
   { "client_id": "oac_01KQRWJRC0OAUTHCLIENT00001", "token_endpoint_auth_method": "none", "redirect_uris": ["http://127.0.0.1:8765/callback"] }
   ```

   Die Registrierung ist auf 10 Aufrufe je Stunde und Absenderadresse begrenzt. Legen Sie die Kennung ab und registrieren Sie nicht bei jedem Start neu.

3. Erzeugen Sie ein Prüfwort und seine Prüfsumme. Das Verfahren ist ausschließlich `S256`; `plain` gibt es nicht.

   ```bash
   VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
   CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -binary -sha256 | openssl base64 | tr '+/' '-_' | tr -d '=')
   ```

4. Schicken Sie die Person in den Browser. Sie landet auf der Einwilligungsseite des Portals und sieht dort den Namen Ihrer Anwendung, das Rücksprung-Ziel und die erbetenen Rechte — gebündelt zu **Bereichen** mit je einem Satz und einem Schalter.

   ```bash
   open "$SZENERA_API/oauth/authorize?response_type=code&client_id=oac_01KQRWJRC0OAUTHCLIENT00001&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&scope=scene.read%20playlist.read&state=$(openssl rand -hex 16)&code_challenge=$CHALLENGE&code_challenge_method=S256"
   ```

   Prüfen Sie beim Rücksprung `state` gegen den gesendeten Wert, bevor Sie den Code einlösen.

5. Tauschen Sie den Code. Der Rumpf ist **formularkodiert**, nicht JSON.

   ```bash
   curl -sS -X POST "$SZENERA_API/oauth/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d grant_type=authorization_code \
     -d code=ocd_01KQRWJRC0OAUTHCODE0000001 \
     -d redirect_uri=http://127.0.0.1:8765/callback \
     -d client_id=oac_01KQRWJRC0OAUTHCLIENT00001 \
     -d code_verifier=$VERIFIER
   ```

   ```json
   { "access_token": "oat_…", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "ort_…", "scope": "playlist.read scene.read" }
   ```

6. Arbeiten Sie damit wie mit jedem anderen Bearer:

   ```bash
   curl -sS "$SZENERA_API/v1/me" -H "Authorization: Bearer oat_…"
   ```

7. Erneuern Sie rechtzeitig. Der Erneuerungs-Token **rotiert**: Nach dem Tausch ist der alte verbraucht, und die Antwort trägt einen neuen.

   ```bash
   curl -sS -X POST "$SZENERA_API/oauth/token" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d grant_type=refresh_token -d refresh_token=ort_… -d client_id=oac_01KQRWJRC0OAUTHCLIENT00001
   ```

8. Geben Sie zurück, was Sie nicht mehr brauchen. Die Rücknahme antwortet immer `200`, auch für einen unbekannten Wert.

   ```bash
   curl -sS -X POST "$SZENERA_API/oauth/revoke" \
     -H "Content-Type: application/x-www-form-urlencoded" \
     -d token=ort_… -d client_id=oac_01KQRWJRC0OAUTHCLIENT00001
   ```

## Optionen

### Lebensdauern

| Gegenstand | Gültig |
|---|---|
| Zugriffstoken `oat_…` | eine Stunde |
| Erneuerungs-Token `ort_…` | dreißig Tage, rotiert bei jedem Tausch |
| Autorisierungs-Code `ocd_…` | zwei Minuten, genau eine Einlösung |
| offene Einwilligungs-Anfrage | zehn Minuten |

### Rechte

Es gibt keinen eigenen Katalog: Ein Scope **ist** ein Rechte-Schlüssel, denselben, den auch ein API-Schlüssel tragen darf — `scene.read`, `playlist.write` und so fort. Die vollständige Liste steht als `scopes_supported` in beiden Wegbeschreibungen; welche Rechte für Maschinen freigegeben sind, sagt auch `GET /v1/permission-catalog` über das Feld `appliesTo`.

Beim Einwilligen greifen zwei Schnitte: Ein Recht, das Maschinen nie tragen, ist nicht erteilbar; ein Recht, das die einwilligende Person selbst nicht hält, ebenfalls nicht. Beides steht mit seinem Grund in seinem Bereich. Erbitten Sie deshalb nur, was Sie wirklich brauchen: Ein zu breiter Wunsch macht die Entscheidung schwerer und wird abgewiesen, statt stillschweigend gekürzt zu werden.

### Bereiche und teilweise Einwilligung

Die Einwilligungsseite zeigt die Rechte nicht als Liste, sondern gebündelt nach der **Kategorie** des Rechte-Katalogs — „Szenen“, „Playlisten und Zeitpläne“, „Medien“, „Displays und Standorte“, „Berichte“. Jeder Bereich trägt einen Satz, was Ihre Anwendung darin tun kann, einen Schalter und aufklappbar die einzelnen Rechte. Alle angefragten Bereiche sind zu Beginn eingeschaltet; dieselbe Bündelung steht später in der Karte **Verbundene Anwendungen**, eingeklappt als Bilanz („5 Bereiche, 18 Rechte“).

**Rechnen Sie mit einer Teilmenge.** Der Mensch darf einzelne Bereiche abschalten, und `POST /v1/oauth/authorizations/{id}/approve` nimmt jede Teilmenge des Erbetenen an. Das Zugriffstoken trägt dann nur die erteilten Rechte, und die Token-Antwort nennt sie unter `scope`:

```json
{ "access_token": "oat_…", "scope": "scene.read", "expires_in": 3600 }
```

Vergleichen Sie `scope` mit dem, worum Sie gebeten haben, statt es vorauszusetzen. Was fehlt, beantwortet die Plattform beim Aufruf mit `403` — nicht beim Tausch. Brauchen Sie einen Bereich wirklich, sagen Sie es der Person im Klartext und schicken Sie sie ein zweites Mal durch den Rundlauf; einen Weg, eine bestehende Einwilligung nachträglich zu erweitern, gibt es bewusst nicht.

### Grenzen

`GET /oauth/authorize`, `POST /oauth/token` und `POST /oauth/revoke` sind auf 60 Aufrufe je Minute und Absenderadresse begrenzt, die beiden Wegbeschreibungen auf 120, die Registrierung auf 10 je Stunde. Der Echtzeit-Kanal bleibt einem delegierten Zugang verschlossen; Ereignisse holen Sie über [Webhooks](/de/webhooks).

Der Betreiber der Plattform kann einen Client sperren. Dann beginnt kein neuer Rundlauf mehr, und eine laufende Erneuerung scheitert; bestehende Zugriffstoken laufen aus, statt zurückgenommen zu werden. Ebenso kann eine Organisation den delegierten Zugang ganz abschalten: Die Einwilligung scheitert dann mit `OAuthAuthorization.DelegationDisabled` — dort, wo der Mensch entscheidet, und nicht erst beim Tausch.

## Ergebnis prüfen

- `GET /v1/me` mit dem Token antwortet `200` und nennt das Konto und die Organisation, in deren Auftrag Sie handeln.
- Die Token-Antwort nennt unter `scope` die tatsächlich erteilten Rechte — sie können weniger sein als die erbetenen.
- Die Person sieht Ihre Anwendung im Portal unter **Einstellungen › Mein Konto › Sicherheit** in der Karte **Verbundene Anwendungen**, aufklappbar nach denselben Bereichen, und kann sie dort mit einem Klick widerrufen.
- Nach einem Widerruf antwortet derselbe Aufruf `401`.

## Wenn etwas nicht klappt

Die Endpunkte des Standards antworten im **Format des Standards** (`{ "error": …, "error_description": … }`) und nicht im [Fehlerformat dieser API](/de/api/error-format). Die Routen unter `/v1` antworten regulär.

| Symptom | Bedeutung | Lösung |
|---|---|---|
| `400 invalid_grant` beim Tausch | Das Prüfwort passt nicht zur Prüfsumme, oder der Code ist abgelaufen. | Rundlauf neu beginnen; Prüfwort und Prüfsumme müssen aus demselben Lauf stammen. |
| `400 invalid_grant`, und alle Token sind weg | Ein Code oder ein Erneuerungs-Token wurde ein zweites Mal eingelöst. Die ganze Token-Familie erlischt: Zwei Inhaber desselben Geheimnisses lassen sich nicht auseinanderhalten. | Den alten Token verwerfen, neu einwilligen lassen — und nie zwei Vorgänge auf demselben Token laufen lassen. |
| `400` ohne Weiterleitung | `redirect_uri` steht nicht in der Liste der Registrierung. Gerade diese Adresse ist das, was nicht bestätigt werden konnte. | Die Adresse bei der Registrierung eintragen und beim Tausch wortgleich mitsenden. |
| `410 OAuthAuthorization.Expired` beim Öffnen der Einwilligungsseite | Die Anfrage ist älter als zehn Minuten. Ausdrücklich nicht „nicht gefunden“. | Die Person erneut hinschicken. |
| `422` mit der Nennung von Rechte-Schlüsseln | Es wurde ein Scope bestätigt, den die Person nicht halten kann oder der für Anwendungen nicht vorgesehen ist. | Den genannten Schlüssel aus der Anfrage nehmen. |
| `401` mitten im Betrieb | Das Zugriffstoken ist älter als eine Stunde, oder die Person hat widerrufen. | Einmal erneuern; scheitert auch das, neu einwilligen lassen. |

Die Codes `OAuthClient.*` und `OAuthAuthorization.*` stehen vollständig — mit Bedeutung, Auslöser und Status — unter [Fehlercodes: Konten und Sitzungen](/de/api/error-codes-accounts); die beiden Ausweis-Codes `Auth.OAuthTokenRevoked` und `Auth.OAuthClientBlocked` unter [Fehlercodes: auf jeder Route möglich](/de/api/error-codes-general).

## Weiterführendes

- [Anmeldung: Schlüssel und Sitzungen](/de/api/authentication)
- [Der MCP-Dienst für KI-Assistenten](/de/api/mcp-service)
- [Handbuch: Verbundene Anwendungen erlauben und widerrufen](https://szenera.com/de/docs/organization/connected-applications)
