Fluxul de conectare
Pas cu pas — autorizezi cu PKCE, schimbi codul, apelezi /connect/key, stochezi cheia de gateway. Cu o notă despre CORS în browser.
Fluxul de conectare este OpenID Connect obișnuit plus un endpoint codai. Când se termină, deții o cheie de gateway codai_… care aparține utilizatorului tău, limitată la consimțământul lui pentru aplicația ta, și apelezi https://ai.codai.ro exact cum descrie documentația gateway-ului.
aplicația ta ──1 redirect──▶ auth.codai.ro/auth ──login + consimțământ──▶ redirect_uri-ul tău?code=…&state=…
aplicația ta ──2 POST /token (code + code_verifier)──▶ { access_token, id_token, … }
aplicația ta ──3 GET /connect/key (Bearer access_token)──▶ { api_key: "codai_…", base_url, … }
aplicația ta ──4 Authorization: Bearer codai_… ──▶ ai.codai.ro/v1/… (în numele utilizatorului)Construiești URL-ul de autorizare
PKCE este obligatoriu: generezi un code_verifier aleatoriu (43 – 128 caractere URL-safe), derivezi code_challenge = base64url(sha256(verifier)) și păstrezi verifier-ul plus un state aleatoriu în sesiunea utilizatorului.
GET https://auth.codai.ro/auth
?client_id=<your client_id>
&response_type=code
&redirect_uri=<a registered redirect URI, exact match>
&scope=openid%20email%20profile%20inference%20keys:manage
&state=<random>
&code_challenge=<S256 of the verifier>
&code_challenge_method=S256Adăugiri opționale: offline_access în scope pentru un refresh token (doar dacă clientul tău are grant-ul refresh_token), nonce dacă validezi ID token-urile în modul strict, acr_values=urn:codai:acr:mfa pentru a cere un al doilea factor, resource=https://ai.codai.ro/v1 pentru a fi explicit despre audiență (este și valoarea implicită).
Utilizatorul se autentifică (sau își creează cont) și vede un ecran de consimțământ care listează scope-urile cerute de tine. Anularea îl trimite înapoi cu ?error=access_denied&error_description=User%20cancelled%20the%20authorization.
Schimbi codul
Înapoi la redirect_uri-ul tău, verifici state, apoi faci POST /token:
curl https://auth.codai.ro/token \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=authorization_code" \
--data-urlencode "code=$CODE" \
--data-urlencode "redirect_uri=https://app.example.com/callback" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "code_verifier=$VERIFIER"Un client confidențial se autentifică și el aici — Authorization: Basic base64(client_id:client_secret) (client_secret_basic), client_secret în formular (client_secret_post) sau un JWT client_assertion (private_key_jwt). Un client public trimite doar client_id.
Răspuns:
{
"access_token": "opaque…",
"id_token": "eyJhbGciOiJSUzI1NiIs…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email profile inference keys:manage",
"refresh_token": "…only with offline_access…"
}Access token-ul este opac și trăiește o oră. Ai nevoie de el pentru exact un apel în plus.
Emiți cheia de gateway
GET /connect/key cu access token-ul. Token-ul trebuie să poarte keys:manage sau inference.
curl https://auth.codai.ro/connect/key \
-H "Authorization: Bearer $ACCESS_TOKEN"{
"api_key": "codai_xxxxxxxx",
"api_key_id": "3d6b…-uuid",
"base_url": "https://ai.codai.ro/v1",
"model": "codai",
"already_issued": false
}Proprietate
Tip
O cheie per consimțământ. IdP-ul nu stochează niciodată textul în clar. Dacă apelezi /connect/key din nou pentru același grant primești { "api_key": null, "already_issued": true, … } — nu mai e nimic de recuperat. Persistă cheia (criptată) în momentul în care o primești. Ai pierdut-o? Trimite utilizatorul din nou prin pasul de autorizare: un consimțământ nou este un grant nou și emite o cheie nouă, iar utilizatorul o poate revoca pe cea veche din hub.
Grant-urile de consimțământ trăiesc 90 de zile; când unul expiră, utilizatorul se reconectează și tu stochezi cheia nouă.
| Status | error | Semnificație |
|---|---|---|
401 | invalid_token | Fără bearer, sau Token unknown or expired. — a trecut o oră sau token-ul a fost revocat. |
403 | insufficient_scope | Token-ului îi lipsesc atât keys:manage cât și inference, sau grant-ul nu poartă niciun scope care permite emiterea de chei. |
400 | invalid_grant | Token-ul nu are un grant de consimțământ în spate (un token în stil client-credentials nu poate emite chei). |
Apelezi gateway-ul în numele utilizatorului
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: connected.api_key, baseURL: connected.base_url }); // base_url se termină deja în /v1
const reply = await client.chat.completions.create({ model: connected.model, messages: [{ role: 'user', content: 'Hello from my app' }] });Consumul ajunge pe planul utilizatorului și apare în hub-ul lui sub cheia etichetată cu numele aplicației tale. Trimite X-Codai-Client: <your-app>/<version> ca lista lui de task-uri să îl atribuie corect.
Apelarea /connect/key dintr-un browser
/connect/key răspunde la preflight-urile CORS doar pentru o listă de origini permise, iar lista este a codai, nu derivată din redirect URI-urile tale. Concret:
- Fără header
Origin(server-la-server, curl, aplicații native) → cererea funcționează și nu se adaugă niciun header CORS. - Origine permisă →
Access-Control-Allow-Origin: <origin>,Vary: Origin; preflight204cuAccess-Control-Allow-Methods: GET, OPTIONS,Access-Control-Allow-Headers: authorization, content-type, x-codai-device-name,Access-Control-Max-Age: 86400. - Orice altă origine → preflight
403 { "error": "origin_not_allowed" }; unGETdirect rulează totuși, dar browser-ul nu poate citi răspunsul.
O aplicație single-page terță nu poate apela /connect/key cross-origin. Fă schimbul de cod și emiterea cheii pe backend-ul tău, apoi dă cheia (sau, mai bine, o sesiune legată de ea) browser-ului. /token este diferit — CORS-ul lui urmează redirect URI-urile înregistrate pentru clienții publici, așa că un SPA poate schimba codul direct și are nevoie de backend doar pentru /connect/key.
Dacă produsul tău are cu adevărat nevoie de un flux doar în browser, cere-ne să adăugăm originea ta în lista permisă la înregistrarea clientului.
Reîmprospătare și revocare
- Cu
offline_access,POST /tokencugrant_type=refresh_token&refresh_token=…&client_id=…returnează un access token nou și — pentru clienții publici întotdeauna, pentru cei confidențiali după 70 % din TTL — un refresh token rotit. Cheia de gateway nu expiră odată cu access token-ul; ai nevoie de un access token nou doar dacă vrei să apelezi/mesau să re-verifici grant-ul. POST /token/revocationcutoken=<refresh_token>încheie sesiunea pe partea ta. Revocarea grant-ului (utilizatorul se deconectează din hub) revocă și cheia de gateway; următorul tău apel cătreai.codai.roeste401 invalid_api_key— cere-i utilizatorului să se conecteze din nou.GET /mecu access token-ul returnează claim-urile pentru scope-urile acordate:sub,email,email_verified,name,picture, plusroleșisid.
Autentificare cu codai
auth.codai.ro este un provider OpenID Connect standard. Îl folosești când aplicația ta trebuie să acționeze în numele unui utilizator codai; folosești o cheie API simplă când nu trebuie.
Scope-uri și claim-uri
Fiecare scope emis de auth.codai.ro, ce vede utilizatorul pe ecranul de consimțământ, ce claim-uri și resurse deblochează fiecare.