OAuth

OAuth

Lass Leute sich mit ihrem Atli-Konto in deinem Client anmelden und handle in ihrem Namen, mit Standard-OAuth 2.1: Discovery, PKCE, rotierende Refresh-Tokens und Widerruf. Jeder Client kann es nutzen: eine App, ein Bot, eine Website, ein Server oder ein KI-Assistent.

Wann du es brauchst

  • KI-Assistenten sind ein solcher Client. Wenn sie sich mit Atlis MCP-Server verbinden, melden sie Leute von selbst auf diese Weise an, du musst für sie also nichts bauen. Derselbe Flow dient jedem Client.

  • Jeder Client, der im Namen einer Person handelt: eine Handy-App, eine Website, ein Bot oder ein Server, der jemandem seine eSIMs zeigt oder eine für ihn kauft, mit seinem eigenen Atli-Konto. Registriere einen eigenen Client, und seine Tokens funktionieren mit Atlis API.

  • Wer sich über einen Client anmeldet, der dir gehört, oder an deiner Partner-Adresse des MCP-Servers, kann dir als deine Empfehlung zugeordnet werden. Das ist standardmäßig ausgeschaltet, und Atli kann es für dein Konto einschalten. So oder so kann dein Client dir die Leute, die er bringt, selbst gutschreiben: sieh dir weiter unten „Die Leute, die du bringst, dir gutschreiben“ an.

  • Verbinden kann sich nur ein Konto, bei dem man sich anderswo wieder anmelden kann: eines mit E-Mail, auch über Google, Apple oder Facebook, oder mit Telegram oder WhatsApp.

So funktioniert es

Atlis API ist der Autorisierungsserver. Anmeldung und Zustimmungsseite liegen auf heyatli.com unter /connect, deine App sieht also nie ein Passwort. Lies alle Endpunkte aus den Metadaten, statt sie fest einzutragen. Die Adressen hier folgen dem Schalter zwischen Sandbox und Produktion.

Registriere einmal einen Client. Dann für jede Person: Schick sie mit einer PKCE-Challenge zum Authorize-Endpunkt, nimm den Code, mit dem sie zurückkommt, tausche ihn gegen Tokens und ruf die API mit dem Access-Token auf. Erneuere ihn, wenn er abläuft, und widerrufe ihn, wenn sie sich abmeldet.

curl https://api.heyatli.com/.well-known/oauth-authorization-server

{
  "issuer": "https://api.heyatli.com",
  "authorization_endpoint": "https://api.heyatli.com/oauth2/authorize",
  "token_endpoint": "https://api.heyatli.com/oauth2/token",
  "revocation_endpoint": "https://api.heyatli.com/oauth2/revoke",
  "registration_endpoint": "https://api.heyatli.com/oauth2/register",
  "jwks_uri": "https://api.heyatli.com/oauth2/jwks",
  "scopes_supported": ["api", "offline_access"],
  "code_challenge_methods_supported": ["S256"],
  "client_id_metadata_document_supported": true,
  …
}

Wofür ein Token gilt

Jede Anfrage nennt als resource (RFC 8707) die Adresse, für die ihr Token gilt, und das Token funktioniert nur dort. Eine Adresse, die nicht dazugehört, oder ein Empfehlungscode, den es nicht gibt, wird mit invalid_target abgelehnt, sodass sich keins von beiden erraten lässt.

AdresseWer es anfordern kannWas das Token tut
Atlis API
https://api.heyatli.com
Nur ein Client, der dir gehört

Ein Bearer-Token für die API, das als die Person handelt, wie eines von /login.

Atlis MCP-Server
https://mcp.heyatli.com
Jeder Client

Lässt einen Assistenten über die MCP-Tools im Namen der Person handeln.

Deine Partner-Adresse
https://mcp.heyatli.com/r/YOUR_CODE
Jeder Client

Dasselbe, und die Person kann dir als deine Empfehlung zugeordnet werden, sobald das für dein Konto eingeschaltet ist.

Einen Client registrieren

Es gibt drei Wege. Für deine eigene App registrierst du einen eigenen Client: Nur diese Art kann Zugriff auf Atlis API anfordern.

  1. Ein eigener Client

    Angemeldet als du selbst: POST /user/oauth-clients. Du kannst 10 haben. Wähle public für alles, was auf dem Gerät einer anderen Person läuft, etwa eine Handy-App oder eine Seite in ihrem Browser, und confidential für einen Server, der ein Geheimnis sicher aufbewahren kann.

    Das client_secret eines vertraulichen Clients steht in dieser Antwort und nirgendwo sonst. Wenn du es verlierst, ersetze es mit POST /user/oauth-clients/{id}/rotate-secret. Dein Konto braucht eine E-Mail-Adresse, weil ein Client einen Empfehlungscode braucht; falls du noch keinen hast, wird einer für dich erstellt.

    curl https://api.heyatli.com/user/oauth-clients \
      -H 'Authorization: Bearer YOUR_TOKEN' \
      -H 'Content-Type: application/json' \
      -d '{ "name": "Travel Bot",
            "redirect_uris": ["https://bot.example/oauth/callback"],
            "client_type": "confidential" }'
    
    {
      "client_id": "atli_client_7f3a9c2e…",
      "client_secret": "…",
      "client_type": "confidential",
      …
    }
  2. Dynamische Registrierung

    POST /oauth2/register (RFC 7591), ohne Anmeldung. So registrieren sich die meisten Assistenten. Erlaubt sind 10 Registrierungen pro Stunde von einer Adresse, und ein Client, mit dem sich nie jemand verbindet, wird nach 30 Tagen gelöscht.

    curl https://api.heyatli.com/oauth2/register \
      -H 'Content-Type: application/json' \
      -d '{ "client_name": "Travel Bot",
            "redirect_uris": ["https://bot.example/oauth/callback"] }'
    
    {
      "client_id": "atli_client_…",
      "token_endpoint_auth_method": "none",
      …
    }
  3. Ein Client-ID-Metadatendokument

    Verwende eine https-Adresse, die du selbst hostest, als client_id und liefere dort ein JSON-Dokument aus, das den Client beschreibt. Atli liest es, wenn sich jemand anmeldet, es gibt also nichts zu registrieren. Die MCP-Spezifikation bevorzugt diesen Weg gegenüber der dynamischen Registrierung.

    // Served at https://bot.example/oauth/client.json
    {
      "client_id": "https://bot.example/oauth/client.json",
      "client_name": "Travel Bot",
      "redirect_uris": ["https://bot.example/oauth/callback"],
      "token_endpoint_auth_method": "none"
    }
  • Redirect-URIs werden exakt verglichen. Jede ist https oder http auf localhost, 127.0.0.1 oder [::1], wo nur der Port abweichen darf, und ohne Fragment. Ein Client kann bis zu 10 haben.

Eine Person anmelden

Der Authorization-Code-Flow mit PKCE (S256), den jeder Client nutzt, ob öffentlich oder vertraulich.

  1. Ein PKCE-Paar erzeugen

    Behalte den Verifier für den Code-Austausch und schick seine SHA-256-Challenge mit der Anfrage. Erzeuge für jede Anmeldung ein neues Paar und einen neuen state.

    const base64url = (bytes) =>
      btoa(String.fromCharCode(...bytes))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    
    const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
    const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
    const challenge = base64url(new Uint8Array(digest));
    const state = base64url(crypto.getRandomValues(new Uint8Array(16)));
  2. Zur Anmeldung schicken

    Öffne den Authorize-Endpunkt in ihrem Browser. scope ist api, und offline_access fügt ein Refresh-Token hinzu. Die Person meldet sich auf heyatli.com/connect an und stimmt zu. Dort sieht sie den Namen deines Clients, wohin er sie zurückschickt und ob sie zur Empfehlung von jemandem würde. Die Anfrage gilt 10 Minuten.

    const url = new URL('https://api.heyatli.com/oauth2/authorize');
    url.search = new URLSearchParams({
      response_type: 'code',
      client_id: CLIENT_ID,
      redirect_uri: 'https://bot.example/oauth/callback',
      scope: 'api offline_access',
      state,
      code_challenge: challenge,
      code_challenge_method: 'S256',
      // Atli's API, for a client you own; or the MCP server, https://mcp.heyatli.com
      resource: 'https://api.heyatli.com',
    }).toString();
    window.location.assign(url);
  3. Den mitgebrachten Code annehmen

    Die Person kommt mit code, deinem state und iss zu deiner Redirect-URI zurück. Prüfe, dass state von dir stammt und iss der Issuer ist. Hat sie abgelehnt, steht stattdessen error=access_denied darin.

    https://bot.example/oauth/callback?code=4bd81f…&state=…&iss=https%3A%2F%2Fapi.heyatli.com
    
    https://bot.example/oauth/callback?error=access_denied&state=…&iss=https%3A%2F%2Fapi.heyatli.com
  4. Den Code gegen Tokens tauschen

    Schick den Code mit dem Verifier, derselben redirect_uri und resource sowie den Zugangsdaten deines Clients, falls er welche hat. Ein Code gilt 60 Sekunden und funktioniert einmal: Wird er zweimal oder mit dem falschen Verifier gesendet, endet der Zugriff der Person, weil jemand anderes ihn haben könnte.

    Das Access-Token gilt expires_in Sekunden, also 15 Minuten. Ein Refresh-Token kommt immer mit.

    curl https://api.heyatli.com/oauth2/token \
      -u 'CLIENT_ID:CLIENT_SECRET' \
      -d grant_type=authorization_code \
      -d code=THE_CODE \
      -d code_verifier=THE_VERIFIER \
      -d redirect_uri=https://bot.example/oauth/callback \
      -d client_id=CLIENT_ID \
      -d resource=https://api.heyatli.com
    
    {
      "access_token": "…",
      "token_type": "Bearer",
      "expires_in": 900,
      "refresh_token": "…",
      "scope": "api offline_access"
    }

Angemeldet bleiben

Wenn das Access-Token abläuft, fordere mit dem Refresh-Token ein neues an. Du kannst einen engeren scope anfordern, nie einen weiteren.

Das Refresh-Token ändert sich bei jeder Nutzung, speichere also jedes Mal das neue. Ist eine Antwort nie bei dir angekommen, liefert das erneute Senden des alten Tokens für kurze Zeit dieselbe Antwort. Danach heißt das alte Token, dass es kopiert wurde, und der Zugriff der Person endet.

curl https://api.heyatli.com/oauth2/token \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d grant_type=refresh_token \
  -d refresh_token=THE_REFRESH_TOKEN \
  -d client_id=CLIENT_ID \
  -d resource=https://api.heyatli.com

Das Token verwenden

Sende es als Authorization: Bearer ACCESS_TOKEN, an die Adresse, für die es ausgestellt wurde, und nirgendwo sonst hin.

Ein Token für Atlis API handelt auf jeder API-Route als die Person, wie ein Token von /login. Behandle es als undurchsichtig: Sein Inhalt kann sich ändern.

Ein Token für einen MCP-Server ist ein mit EdDSA signiertes JWT. Der Server prüft es anhand seiner kid gegen die Schlüssel unter /oauth2/jwks und prüft, dass sein aud die eigene Adresse ist. Es enthält keine E-Mail und keine anderen Angaben: nur sub (die ID der Person), scope, grant_id und die Zeiten.

curl https://api.heyatli.com/esims \
  -H 'Authorization: Bearer ACCESS_TOKEN'

Die Leute, die du bringst, dir gutschreiben

Dass dir die Leute zugeordnet werden, die sich über deinen Client oder an deiner Partner-Adresse anmelden, ist standardmäßig ausgeschaltet, und Atli kann es für dein Konto einschalten. Ob es eingeschaltet ist oder nicht: Dein Client kann eine angemeldete Person selbst mit deinem Empfehlungscode verknüpfen, mit ihrem Access-Token: POST /user/referral/redeem mit deinem code.

result sagt, was passiert ist: ok hat die Person verknüpft, already heißt, sie hat schon einen Werber, notnew, dass sie bereits gekauft hat, own, dass es dein eigener Code ist, und notfound, dass es den Code nicht gibt. Verknüpft werden kann nur eine Person ohne Werber, die noch nichts gekauft hat, und nur einmal.

curl https://api.heyatli.com/user/referral/redeem \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{ "code": "YOUR_CODE" }'

{
  "success": true,
  "result": "ok",
  "message": "…",
  "discount_percent": 10
}

Abmelden und Zugriff entziehen

Wenn sich jemand von deiner App abmeldet, widerrufe sein Refresh-Token. Das beendet den Zugriff in deinem Client, mit allen Tokens darauf. Der Endpunkt antwortet auch bei einem unbekannten Token mit 200, damit sich darüber nicht herausfinden lässt, welche Tokens existieren.

Leute können auch selbst sehen, welche Apps sie verbunden haben, und ihnen den Zugriff entziehen (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). Ab dann bekommt dein Client kein neues Token mehr, behandle eine abgelehnte Erneuerung also als Abmeldung.

curl https://api.heyatli.com/oauth2/revoke \
  -u 'CLIENT_ID:CLIENT_SECRET' \
  -d token=THE_REFRESH_TOKEN \
  -d client_id=CLIENT_ID

Fehler

Die Token-, Widerrufs- und Registrierungs-Endpunkte antworten mit dem eigenen Fehlerformat von OAuth, {"error": "invalid_grant", "error_description": "…"}, statt mit dem üblichen der API. Verzweige nach error, nie nach der Beschreibung. Jede Antwort des Token-Endpunkts enthält Cache-Control: no-store.

  • invalid_grant: Der Code wurde schon benutzt oder ist abgelaufen, der Verifier ist falsch, oder ein Refresh-Token wurde nach seiner Kulanzfrist erneut verwendet. Melde die Person erneut an.

  • invalid_target: resource ist keine Adresse, für die ein Token gelten kann, oder nennt einen Empfehlungscode, den es nicht gibt.

  • invalid_client: Die Zugangsdaten des Clients sind falsch oder fehlen. Jede Art schlägt gleich fehl.

  • invalid_scope: Eine Erneuerung hat mehr angefordert, als die Person erlaubt hat.