OAuth

OAuth

Pozwól ludziom logować się do Twojego klienta kontem Atli i działaj w ich imieniu, przez standardowy OAuth 2.1: discovery, PKCE, rotowane tokeny odświeżania i unieważnianie. Może z niego korzystać każdy klient: aplikacja, bot, strona internetowa, serwer albo asystent AI.

Kiedy tego użyć

  • Asystenci AI to jeden z takich klientów. Łącząc się z serwerem MCP Atli, sami logują ludzi w ten sposób, więc nie trzeba dla nich niczego budować. Ten sam przepływ służy każdemu klientowi.

  • Każdy klient, który działa w imieniu osoby: aplikacja na telefon, strona internetowa, bot albo serwer, który pokazuje komuś jego eSIM-y lub kupuje mu jeden, z jego własnym kontem Atli. Zarejestruj własnego klienta, a jego tokeny zadziałają w API Atli.

  • Osoby, które logują się przez należącego do Ciebie klienta albo pod Twoim adresem partnerskim serwera MCP, mogą zostać przypisane Tobie jako Twoje polecone. Domyślnie jest to wyłączone, a Atli może to włączyć dla Twojego konta. Tak czy inaczej, Twój klient może sam przypisać Ci osoby, które sprowadza: zobacz niżej „Przypisz sobie osoby, które sprowadzasz”.

  • Połączyć można tylko konto, do którego da się zalogować ponownie gdzie indziej: z adresem e-mail, także przez Google, Apple lub Facebook, albo z Telegramem lub WhatsAppem.

Jak to działa

Serwerem autoryzacji jest API Atli. Logowanie i ekran zgody są na heyatli.com, pod /connect, więc Twoja aplikacja nigdy nie widzi hasła. Odczytuj każdy endpoint z metadanych, zamiast wpisywać je na sztywno. Adresy na tej stronie zmieniają się razem z przełącznikiem Sandbox i Produkcja.

Zarejestruj klienta raz. Potem dla każdej osoby: wyślij ją do endpointu autoryzacji z challenge’em PKCE, odbierz kod, z którym wraca, wymień go na tokeny i wywołuj API z tokenem dostępu. Odśwież go, gdy wygaśnie, i unieważnij, gdy osoba się wyloguje.

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,
  …
}

Do czego służy token

Każde żądanie podaje adres, dla którego jest jego token, jako resource (RFC 8707), i token działa tylko tam. Adres spoza tej listy albo nieistniejący kod polecający jest odrzucany z invalid_target, więc żadnego z nich nie da się zgadnąć.

AdresKto może o niego poprosićCo robi token
API Atli
https://api.heyatli.com
Tylko klient należący do Ciebie

Token bearer do API, działający jako ta osoba, tak jak token z /login.

Serwer MCP Atli
https://mcp.heyatli.com
Każdy klient

Pozwala asystentowi działać w imieniu osoby przez narzędzia MCP.

Twój adres partnerski
https://mcp.heyatli.com/r/YOUR_CODE
Każdy klient

To samo, a osoba może zostać przypisana Tobie jako Twoja polecona, gdy zostanie to włączone dla Twojego konta.

Zarejestruj klienta

Są trzy sposoby. Dla własnej aplikacji zarejestruj własnego klienta: tylko taki może prosić o dostęp do API Atli.

  1. Własny klient

    Po zalogowaniu na swoje konto wywołaj POST /user/oauth-clients. Możesz mieć 10. Wybierz public dla wszystkiego, co działa na cudzym urządzeniu, np. aplikacji na telefon albo strony w czyjejś przeglądarce, a confidential dla serwera, który potrafi przechować sekret.

    client_secret klienta poufnego jest w tej odpowiedzi i nigdzie indziej. Jeśli go zgubisz, zastąp go przez POST /user/oauth-clients/{id}/rotate-secret. Twoje konto musi mieć adres e-mail, bo klient potrzebuje kodu polecającego; jeśli jeszcze go nie masz, zostanie dla Ciebie utworzony.

    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. Rejestracja dynamiczna

    POST /oauth2/register (RFC 7591), bez logowania. Tak rejestruje się większość asystentów. Pozwala na 10 rejestracji na godzinę z jednego adresu, a klient, z którym nikt nigdy się nie połączy, jest usuwany po 30 dniach.

    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. Dokument metadanych identyfikatora klienta

    Użyj jako client_id adresu https, który sam hostujesz i który serwuje dokument JSON opisujący klienta. Atli odczytuje go, gdy ktoś się loguje, więc nie trzeba niczego rejestrować. Specyfikacja MCP woli ten sposób od rejestracji dynamicznej.

    // 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"
    }
  • Adresy URI przekierowania są porównywane dokładnie. Każdy to https albo http na localhost, 127.0.0.1 lub [::1], gdzie może się różnić tylko port, i bez fragmentu. Klient może mieć ich do 10.

Zaloguj osobę

Przepływ authorization code z PKCE (S256), którego używa każdy klient, publiczny czy poufny.

  1. Utwórz parę PKCE

    Zachowaj verifier do wymiany kodu, a jego challenge SHA-256 wyślij z żądaniem. Twórz nową parę i nowy state przy każdym logowaniu.

    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. Wyślij ją do logowania

    Otwórz endpoint autoryzacji w jej przeglądarce. scope to api, a offline_access dodaje token odświeżania. Osoba loguje się i wyraża zgodę na heyatli.com/connect, gdzie widzi nazwę Twojego klienta, dokąd ją odeśle i czy stałaby się czyjąś poleconą. Żądanie jest ważne 10 minut.

    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. Odbierz kod, z którym wraca

    Wraca na Twój URI przekierowania z code, Twoim state i iss. Sprawdź, czy state jest Twój i czy iss to wystawca. Jeśli odmówiła, zamiast tego jest tam error=access_denied.

    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. Wymień kod na tokeny

    Wyślij kod z verifierem, tym samym redirect_uri i resource oraz danymi uwierzytelniającymi klienta, jeśli je ma. Kod jest ważny 60 sekund i działa raz: wysłanie go dwa razy albo z błędnym verifierem kończy dostęp tej osoby, bo ktoś inny może go mieć.

    Token dostępu jest ważny expires_in sekund, czyli 15 minut. Zawsze przychodzi z nim token odświeżania.

    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"
    }

Utrzymaj zalogowanie

Gdy token dostępu wygaśnie, poproś o nowy tokenem odświeżania. Możesz poprosić o węższy scope, nigdy o szerszy.

Token odświeżania zmienia się przy każdym użyciu, więc za każdym razem zapisuj nowy. Jeśli odpowiedź do Ciebie nie dotarła, ponowne wysłanie starego tokenu przez krótki czas zwraca tę samą odpowiedź. Potem stary token oznacza, że został skopiowany, i dostęp tej osoby się kończy.

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

Użyj tokenu

Wysyłaj go jako Authorization: Bearer ACCESS_TOKEN, pod adres, dla którego został wydany, i nigdzie indziej.

Token do API Atli działa jako ta osoba na każdej ścieżce API, tak jak token z /login. Traktuj go jako nieprzezroczysty: jego zawartość może się zmienić.

Token dla serwera MCP to JWT podpisany EdDSA. Serwer sprawdza go kluczami z /oauth2/jwks według jego kid i sprawdza, czy jego aud to własny adres serwera. Nie zawiera e-maila ani innych danych: tylko sub (id osoby), scope, grant_id i czasy.

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

Przypisz sobie osoby, które sprowadzasz

Przypisywanie Tobie osób, które logują się przez Twojego klienta albo pod Twoim adresem partnerskim, jest domyślnie wyłączone, a Atli może je włączyć dla Twojego konta. Niezależnie od tego, czy jest włączone, Twój klient może sam powiązać zalogowaną osobę z Twoim kodem polecającym, używając jej tokenu dostępu: POST /user/referral/redeem z Twoim code.

result mówi, co się stało: ok powiązało osobę, already oznacza, że ma już polecającego, notnew, że już wcześniej kupowała, own, że to Twój własny kod, a notfound, że taki kod nie istnieje. Powiązać można tylko osobę bez polecającego, która jeszcze nic nie kupiła, i tylko raz.

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
}

Wylogowanie i odebranie dostępu

Gdy ktoś wyloguje się z Twojej aplikacji, unieważnij jego token odświeżania. To kończy jego dostęp w Twoim kliencie, razem ze wszystkimi tokenami. Endpoint odpowiada 200 nawet dla nieznanego tokenu, więc nie da się nim sprawdzić, jakie tokeny istnieją.

Ludzie mogą też sami zobaczyć połączone aplikacje i odebrać im dostęp (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). Od tej chwili Twój klient nie dostanie nowego tokenu, więc traktuj odrzucone odświeżenie jak wylogowanie.

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

Błędy

Endpointy tokenu, unieważniania i rejestracji odpowiadają własnym formatem błędu OAuth, {"error": "invalid_grant", "error_description": "…"}, a nie zwykłym formatem API. Rozgałęziaj logikę po error, nigdy po opisie. Każda odpowiedź endpointu tokenu zawiera Cache-Control: no-store.

  • invalid_grant: kod został już użyty albo wygasł, verifier jest błędny albo token odświeżania użyto ponownie po okresie karencji. Zaloguj osobę od nowa.

  • invalid_target: resource nie jest adresem, dla którego może być token, albo wskazuje nieistniejący kod polecający.

  • invalid_client: dane uwierzytelniające klienta są błędne lub ich brak. Każdy rodzaj kończy się tym samym błędem.

  • invalid_scope: odświeżenie poprosiło o więcej, niż osoba zezwoliła.