OAuth

OAuth

Fai accedere le persone al tuo client con il loro account Atli, e agisci per loro conto, con OAuth 2.1 standard: discovery, PKCE, refresh token che ruotano e revoca. Può usarlo qualsiasi client: un’app, un bot, un sito web, un server o un assistente IA.

Quando usarlo

  • Gli assistenti IA sono uno di questi client. Collegandosi al server MCP di Atli, fanno accedere le persone in questo modo da soli, quindi per loro non c’è niente da costruire. Lo stesso flusso vale per ogni client.

  • Qualsiasi client che agisce per una persona: un’app per telefono, un sito web, un bot o un server che mostra a qualcuno le sue eSIM o ne acquista una per lui, con il suo account Atli. Registra un tuo client, e i suoi token funzionano sull’API di Atli.

  • Chi accede tramite un client di tua proprietà, o al tuo indirizzo partner del server MCP, può essere collegato a te come tuo referral. È disattivato per impostazione predefinita, e Atli può attivarlo per il tuo account. In ogni caso, il tuo client può farti attribuire da sé le persone che porta: vedi «Fatti attribuire le persone che porti», più sotto.

  • Può collegarsi solo un account a cui si possa accedere di nuovo altrove: uno con un’email, anche tramite Google, Apple o Facebook, oppure con Telegram o WhatsApp.

Come funziona

L’API di Atli è il server di autorizzazione. L’accesso e la schermata di consenso sono su heyatli.com, in /connect, quindi la tua app non vede mai una password. Leggi ogni endpoint dai metadati invece di scriverli a mano. Gli indirizzi in questa pagina seguono il selettore Sandbox e Produzione.

Registra un client una volta. Poi, per ogni persona: mandala all’endpoint di autorizzazione con una challenge PKCE, prendi il codice con cui torna, scambialo con i token e chiama l’API con l’access token. Rinnovalo quando scade e revocalo quando la persona esce.

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

A cosa serve un token

Ogni richiesta indica l’indirizzo a cui è destinato il suo token, come resource (RFC 8707), e il token funziona solo lì. Un indirizzo che non sia uno di questi, o un codice referral che non esiste, viene rifiutato con invalid_target, così nessuno dei due può essere indovinato.

IndirizzoChi può richiederloCosa fa il token
L’API di Atli
https://api.heyatli.com
Solo un client di tua proprietà

Un bearer token per l’API, che agisce come la persona, come quello di /login.

Il server MCP di Atli
https://mcp.heyatli.com
Qualsiasi client

Permette a un assistente di agire per conto della persona tramite gli strumenti MCP.

Il tuo indirizzo partner
https://mcp.heyatli.com/r/YOUR_CODE
Qualsiasi client

Lo stesso, e la persona può essere collegata a te come tuo referral, una volta attivata questa opzione per il tuo account.

Registrare un client

Ci sono tre modi. Per la tua app, registra un tuo client: è l’unico tipo che può richiedere l’API di Atli.

  1. Un tuo client

    Dopo aver effettuato l’accesso con il tuo account, POST /user/oauth-clients. Puoi averne 10. Scegli public per tutto ciò che gira sul dispositivo di qualcun altro, come un’app per telefono o una pagina nel suo browser, e confidential per un server in grado di custodire un segreto.

    Il client_secret di un client confidenziale si trova in questa risposta e da nessun’altra parte. Se lo perdi, sostituiscilo con POST /user/oauth-clients/{id}/rotate-secret. Il tuo account deve avere un’email, perché un client ha bisogno di un codice referral; se non ne hai ancora uno, viene creato per te.

    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. Registrazione dinamica

    POST /oauth2/register (RFC 7591), senza accesso. È così che si registra la maggior parte degli assistenti. Consente 10 registrazioni all’ora da uno stesso indirizzo, e un client a cui nessuno si collega mai viene eliminato dopo 30 giorni.

    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. Un documento di metadati del client ID

    Usa come client_id un indirizzo https ospitato da te, che serve un documento JSON che descrive il client. Atli lo legge quando qualcuno accede, quindi non c’è niente da registrare. La specifica MCP preferisce questo metodo alla registrazione dinamica.

    // 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"
    }
  • Gli URI di reindirizzamento devono corrispondere esattamente. Ognuno è https, oppure http su localhost, 127.0.0.1 o [::1], dove può cambiare solo la porta, senza frammento. Un client può averne fino a 10.

Far accedere una persona

Il flusso authorization code con PKCE (S256), che usano tutti i client, pubblici o confidenziali.

  1. Crea una coppia PKCE

    Conserva il verifier per lo scambio del codice e invia la sua challenge SHA-256 con la richiesta. Crea una nuova coppia, e un nuovo state, per ogni accesso.

    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. Mandala ad accedere

    Apri l’endpoint di autorizzazione nel suo browser. scope è api, e offline_access aggiunge un refresh token. La persona accede e acconsente su heyatli.com/connect, che mostra il nome del tuo client, dove la rimanda e se diventerebbe il referral di qualcuno. La richiesta dura 10 minuti.

    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. Prendi il codice che riporta

    Torna al tuo URI di reindirizzamento con code, il tuo state e iss. Verifica che state sia tuo e che iss sia l’issuer. Se ha rifiutato, contiene invece 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. Scambia il codice con i token

    Invia il codice con il verifier, lo stesso redirect_uri e la stessa resource, e le credenziali del tuo client se ne ha. Un codice dura 60 secondi e funziona una volta sola: inviarlo due volte, o con il verifier sbagliato, chiude l’accesso della persona, perché qualcun altro potrebbe averlo.

    L’access token dura expires_in secondi, cioè 15 minuti. Arriva sempre insieme a un refresh token.

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

Mantenere l’accesso

Quando l’access token scade, chiedine uno nuovo con il refresh token. Puoi chiedere uno scope più ristretto, mai uno più ampio.

Il refresh token cambia a ogni utilizzo, quindi conserva ogni volta quello nuovo. Se una risposta non ti è mai arrivata, inviare di nuovo il vecchio token per un breve periodo restituisce la stessa risposta. Dopo, il vecchio token significa che è stato copiato, e l’accesso della persona termina.

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

Usare il token

Invialo come Authorization: Bearer ACCESS_TOKEN, all’indirizzo per cui è stato emesso e a nessun altro.

Un token per l’API di Atli agisce come la persona su ogni route dell’API, come un token di /login. Trattalo come opaco: il suo contenuto può cambiare.

Un token per un server MCP è un JWT firmato con EdDSA. Il server lo verifica con le chiavi in /oauth2/jwks, in base al suo kid, e controlla che il suo aud sia il proprio indirizzo. Non contiene email né altri dati: solo sub (l’id della persona), scope, grant_id e gli orari.

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

Fatti attribuire le persone che porti

Il collegamento a te delle persone che accedono tramite il tuo client, o al tuo indirizzo partner, è disattivato per impostazione predefinita, e Atli può attivarlo per il tuo account. Che sia attivo o no, il tuo client può collegare da sé una persona che ha effettuato l’accesso al tuo codice referral, con il suo access token: POST /user/referral/redeem con il tuo code.

result dice cos’è successo: ok l’ha collegata, already significa che ha già un referente, notnew che ha già acquistato, own che è il tuo stesso codice e notfound che il codice non esiste. Si può collegare solo una persona senza referente che non ha ancora acquistato, e una volta sola.

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
}

Uscire e revocare l’accesso

Quando qualcuno esce dalla tua app, revoca il suo refresh token. Questo chiude il suo accesso nel tuo client, con tutti i token collegati. Risponde 200 anche per un token che non conosce, così non può essere usato per scoprire quali token esistono.

Le persone possono anche vedere le app che hanno collegato e revocare loro l’accesso da sole (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). Da quel momento il tuo client non può ottenere un nuovo token, quindi tratta un rinnovo rifiutato come un’uscita.

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

Errori

Gli endpoint di token, revoca e registrazione rispondono con il formato di errore proprio di OAuth, {"error": "invalid_grant", "error_description": "…"}, invece di quello abituale dell’API. Basati su error, mai sulla descrizione. Ogni risposta dell’endpoint di token contiene Cache-Control: no-store.

  • invalid_grant: il codice è già stato usato o è scaduto, il verifier è sbagliato, oppure un refresh token è stato riusato dopo il suo periodo di tolleranza. Fai accedere di nuovo la persona.

  • invalid_target: resource non è un indirizzo a cui può essere destinato un token, oppure indica un codice referral che non esiste.

  • invalid_client: le credenziali del client sono sbagliate o mancanti. Ogni tipo fallisce allo stesso modo.

  • invalid_scope: un rinnovo ha chiesto più di quanto la persona ha consentito.