codai docs
Autentificare

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=S256

Adă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ă.

StatuserrorSemnificație
401invalid_tokenFără bearer, sau Token unknown or expired. — a trecut o oră sau token-ul a fost revocat.
403insufficient_scopeToken-ului îi lipsesc atât keys:manage cât și inference, sau grant-ul nu poartă niciun scope care permite emiterea de chei.
400invalid_grantToken-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; preflight 204 cu Access-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" }; un GET direct 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 /token cu grant_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 /me sau să re-verifici grant-ul.
  • POST /token/revocation cu token=<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ătre ai.codai.ro este 401 invalid_api_key — cere-i utilizatorului să se conecteze din nou.
  • GET /me cu access token-ul returnează claim-urile pentru scope-urile acordate: sub, email, email_verified, name, picture, plus role și sid.

Pe această pagină