OAuth

OAuth

Biarkan orang masuk ke klienmu dengan akun Atli mereka, dan bertindak atas nama mereka, dengan OAuth 2.1 standar: discovery, PKCE, refresh token yang berotasi, dan pencabutan. Klien apa pun bisa memakainya: aplikasi, bot, situs web, server, atau asisten AI.

Kapan menggunakannya

  • Asisten AI adalah salah satu klien seperti itu. Saat terhubung ke server MCP Atli, mereka memasukkan orang dengan cara ini dengan sendirinya, jadi tidak ada yang perlu dibuat untuk mereka. Alur yang sama melayani setiap klien.

  • Klien apa pun yang bertindak untuk seseorang: aplikasi ponsel, situs web, bot, atau server yang menampilkan eSIM seseorang atau membelikannya, dengan akun Atli milik orang itu sendiri. Daftarkan klien milikmu sendiri, dan token-nya bisa dipakai di API Atli.

  • Orang yang masuk melalui klien milikmu, atau di alamat partnermu pada server MCP, bisa dikaitkan denganmu sebagai referralmu. Fitur ini nonaktif secara default, dan Atli bisa mengaktifkannya untuk akunmu. Apa pun pengaturannya, klienmu bisa sendiri mencatat orang yang dibawanya sebagai referralmu: lihat "Dapatkan kredit atas orang yang kamu bawa" di bawah.

  • Hanya akun yang bisa dimasuki lagi di tempat lain yang bisa terhubung: akun dengan email, termasuk lewat Google, Apple, atau Facebook, atau dengan Telegram atau WhatsApp.

Cara kerjanya

API Atli adalah server otorisasi. Proses masuk dan layar persetujuan ada di heyatli.com, di /connect, jadi aplikasimu tidak pernah melihat kata sandi. Baca setiap endpoint dari metadata, jangan ditulis langsung di kode. Alamat di sini mengikuti pengalih Sandbox dan Produksi.

Daftarkan klien sekali. Lalu, untuk setiap orang: arahkan mereka ke endpoint otorisasi dengan challenge PKCE, ambil kode yang mereka bawa kembali, tukarkan dengan token, dan panggil API dengan access token. Perbarui saat habis masa berlakunya, dan cabut saat mereka keluar.

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

Untuk apa sebuah token

Setiap permintaan menyebutkan alamat tujuan token-nya, sebagai resource (RFC 8707), dan token hanya berfungsi di sana. Alamat yang bukan salah satu dari ini, atau kode referral yang tidak ada, ditolak dengan invalid_target, sehingga keduanya tidak bisa ditebak.

AlamatSiapa yang bisa memintanyaApa yang dilakukan token
API Atli
https://api.heyatli.com
Hanya klien milikmu

Bearer token untuk API, yang bertindak sebagai orang tersebut, seperti token dari /login.

Server MCP Atli
https://mcp.heyatli.com
Klien mana pun

Memungkinkan asisten bertindak atas nama orang tersebut melalui alat MCP.

Alamat partnermu
https://mcp.heyatli.com/r/YOUR_CODE
Klien mana pun

Sama, dan orang tersebut bisa dikaitkan denganmu sebagai referralmu, setelah fitur itu diaktifkan untuk akunmu.

Mendaftarkan klien

Ada tiga cara. Untuk aplikasimu sendiri, daftarkan klien milikmu sendiri: hanya jenis ini yang bisa meminta API Atli.

  1. Klien milikmu sendiri

    Setelah masuk dengan akunmu sendiri, panggil POST /user/oauth-clients. Kamu bisa punya 10. Pilih public untuk apa pun yang berjalan di perangkat orang lain, seperti aplikasi ponsel atau halaman di browser mereka, dan confidential untuk server yang bisa menyimpan rahasia.

    client_secret milik klien rahasia ada di respons ini dan tidak di tempat lain. Jika hilang, ganti dengan POST /user/oauth-clients/{id}/rotate-secret. Akunmu harus punya email, karena klien memerlukan kode referral; kode akan dibuatkan jika kamu belum punya.

    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. Pendaftaran dinamis

    POST /oauth2/register (RFC 7591), tanpa masuk. Begitulah kebanyakan asisten mendaftar. Ini mengizinkan 10 pendaftaran per jam dari satu alamat, dan klien yang tidak pernah dihubungkan siapa pun dihapus setelah 30 hari.

    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. Dokumen metadata client ID

    Gunakan alamat https yang kamu hosting sebagai client_id, yang menyajikan dokumen JSON berisi deskripsi klien. Atli membacanya saat seseorang masuk, jadi tidak ada yang perlu didaftarkan. Spesifikasi MCP lebih menyarankan cara ini daripada pendaftaran dinamis.

    // 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 URI dicocokkan secara persis. Masing-masing berupa https, atau http di localhost, 127.0.0.1, atau [::1], yang hanya boleh berbeda port-nya, tanpa fragmen. Satu klien bisa punya hingga 10.

Memasukkan seseorang

Alur authorization code dengan PKCE (S256), yang dipakai setiap klien, publik maupun rahasia.

  1. Buat pasangan PKCE

    Simpan verifier untuk penukaran kode, dan kirim challenge SHA-256-nya bersama permintaan. Buat pasangan baru, dan state baru, untuk setiap proses masuk.

    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. Arahkan mereka untuk masuk

    Buka endpoint otorisasi di browser mereka. scope adalah api, dan offline_access menambahkan refresh token. Mereka masuk dan menyetujui di heyatli.com/connect, yang menampilkan nama klienmu, ke mana klien itu mengembalikan mereka, dan apakah mereka akan menjadi referral seseorang. Permintaan berlaku 10 menit.

    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. Ambil kode yang mereka bawa kembali

    Mereka kembali ke redirect URI-mu dengan code, state milikmu, dan iss. Pastikan state milikmu dan iss adalah penerbitnya. Jika mereka menolak, yang dibawa adalah 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. Tukarkan kode dengan token

    Kirim kode bersama verifier, redirect_uri dan resource yang sama, serta kredensial klienmu jika ada. Kode berlaku 60 detik dan hanya bisa dipakai sekali: mengirimnya dua kali, atau dengan verifier yang salah, mengakhiri akses orang tersebut, karena mungkin orang lain memilikinya.

    Access token berlaku expires_in detik, yaitu 15 menit. Refresh token selalu ikut bersamanya.

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

Menjaga mereka tetap masuk

Saat access token habis masa berlakunya, minta yang baru dengan refresh token. Kamu bisa meminta scope yang lebih sempit, tetapi tidak pernah yang lebih luas.

Refresh token berubah setiap kali dipakai, jadi simpan yang baru setiap kali. Jika sebuah respons tidak pernah sampai kepadamu, mengirim token lama lagi dalam waktu singkat akan mendapat respons yang sama. Setelah itu, token lama berarti token tersebut telah disalin, dan akses orang tersebut berakhir.

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

Menggunakan token

Kirim sebagai Authorization: Bearer ACCESS_TOKEN, ke alamat tempat token itu diterbitkan dan tidak ke tempat lain.

Token untuk API Atli bertindak sebagai orang tersebut di setiap rute API, seperti token dari /login. Perlakukan sebagai opak: isinya bisa berubah.

Token untuk server MCP adalah JWT yang ditandatangani dengan EdDSA. Server memeriksanya dengan kunci di /oauth2/jwks, berdasarkan kid-nya, dan memastikan aud-nya adalah alamat server itu sendiri. Token ini tidak berisi email atau detail lain: hanya sub (id orang tersebut), scope, grant_id, dan waktu-waktunya.

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

Dapatkan kredit atas orang yang kamu bawa

Mengaitkan orang yang masuk melalui klienmu, atau di alamat partnermu, denganmu nonaktif secara default, dan Atli bisa mengaktifkannya untuk akunmu. Aktif atau tidak, klienmu bisa sendiri mengaitkan orang yang sudah masuk dengan kode referralmu, menggunakan access token mereka: POST /user/referral/redeem dengan code milikmu.

result menjelaskan apa yang terjadi: ok berarti mereka sudah dikaitkan, already berarti mereka sudah punya perujuk, notnew berarti mereka pernah membeli, own berarti itu kodemu sendiri, dan notfound berarti kode itu tidak ada. Hanya orang tanpa perujuk yang belum pernah membeli yang bisa dikaitkan, dan hanya sekali.

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
}

Keluar dan mencabut akses

Saat seseorang keluar dari aplikasimu, cabut refresh token mereka. Itu mengakhiri akses mereka di klienmu, beserta semua token di dalamnya. Endpoint ini menjawab 200 bahkan untuk token yang tidak dikenalnya, sehingga tidak bisa dipakai untuk mencari tahu token mana yang ada.

Orang juga bisa melihat aplikasi yang mereka hubungkan dan mencabut aksesnya sendiri (GET /user/oauth-grants, DELETE /user/oauth-grants/{id}). Sejak itu klienmu tidak bisa mendapatkan token baru, jadi anggap pembaruan yang ditolak sebagai keluar.

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

Error

Endpoint token, pencabutan, dan pendaftaran menjawab dengan format error milik OAuth sendiri, {"error": "invalid_grant", "error_description": "…"}, bukan format API yang biasa. Buat percabangan berdasarkan error, jangan pernah berdasarkan deskripsi. Setiap respons dari endpoint token membawa Cache-Control: no-store.

  • invalid_grant: kode sudah dipakai atau habis masa berlakunya, verifier salah, atau refresh token dipakai lagi setelah masa tenggangnya. Minta orang tersebut masuk lagi.

  • invalid_target: resource bukan alamat yang bisa menjadi tujuan token, atau menyebut kode referral yang tidak ada.

  • invalid_client: kredensial klien salah atau tidak ada. Semua jenis gagal dengan cara yang sama.

  • invalid_scope: pembaruan meminta lebih dari yang diizinkan orang tersebut.