OAuth 2.0: ce este, flow-urile principale și PKCE pe scurt
OAuth 2.0 nu este autentificare, ci un protocol de delegare a accesului. Flow-uri, PKCE, diferențe față de JWT și capcanele clasice.
Cuprins
OAuth 2.0 (RFC 6749, 2012) este un protocol de delegare a accesului la resurse, nu un mecanism de autentificare. Permite unui utilizator să acorde unei aplicații terțe acces limitat la resursele sale de pe un alt serviciu, fără să îi dea parola. Confuzia cu autentificarea este sistematică și are consecințe practice: echipe care implementează OAuth 2.0 ca substitut pentru autentificare introduc vulnerabilități pe care nu le anticipează.
Distincția fundamentală: OAuth spune cine are voie să facă ce, nu cine ești. OpenID Connect (OIDC) este stratul de autentificare construit ulterior peste OAuth 2.0, care adaugă un ID token cu identitatea verificată a utilizatorului. Dacă ai nevoie de „login cu Google", folosești de fapt OIDC, nu OAuth gol.
Ce este OAuth 2.0 mai exact?
Un protocol de autorizare care definește cum un resource owner (utilizatorul) poate acorda unui client (aplicația ta) acces la resursele sale gestionate de un resource server, prin intermediul unui authorization server. Fiecare actor are un rol precis:
- Resource owner: utilizatorul care deține resursa. În OAuth, el decide ce accesezi și cât timp.
- Client: aplicația care solicită accesul. Poate fi publică (SPA, aplicație mobilă, fără secret persistent) sau confidențială (backend, cu secret securizat).
- Authorization server: emite token-uri după ce utilizatorul și-a dat consimțământul. Google, Microsoft, Keycloak, Auth0 sunt authorization server-e cunoscute.
- Resource server: API-ul care protejează resursele și verifică token-urile la fiecare cerere.
Accesul delegat se face printr-un access token cu durată scurtă și un set de scope-uri care limitează ce operații sunt permise. Un access token cu scope read:email nu poate trimite email-uri, chiar dacă utilizatorul are permisiunea să o facă. Limitarea explicită a scope-urilor este mecanismul central de securitate al protocolului.
Care sunt cele patru flow-uri principale?
RFC 6749 definește mai multe tipuri de grant, fiecare optimizat pentru un tip de client. Cele patru pe care le întâlnești în practică:
- Authorization Code. Flow-ul standard pentru aplicații cu utilizatori prin browser. Utilizatorul este redirecționat la authorization server, se autentifică, consimte, și primește un authorization code de scurtă durată. Clientul schimbă codul pe un access token (și opțional un refresh token) printr-un apel server-to-server. Codul este de unică folosință și expiră rapid (de obicei sub 10 minute). Recomandat cu PKCE în orice implementare nouă, inclusiv pentru clienții confidențiali.
- Client Credentials. Flow pentru scenarii server-to-server, fără utilizator implicat. Clientul se autentifică direct cu clientId + clientSecret și primește un access token. Folosit pentru microservicii care comunică între ele sau pentru daemon-uri care accesează un API. Nu produce refresh tokens, pentru că nu există sesiune de utilizator de reînnoit.
- Device Code. Flow pentru dispozitive fără browser sau cu input limitat: televizoare, console IoT, CLI-uri. Dispozitivul afișează un cod scurt și un URL de autorizare; utilizatorul deschide URL-ul pe alt dispozitiv (telefon, laptop), se autentifică și aprobă. Dispozitivul face polling la authorization server până primește token-ul sau până expiră codul.
- Refresh Token Grant. Nu este un flow inițial de autorizare, ci un mecanism de reînnoire. Când access token-ul expiră, clientul trimite refresh token-ul (obținut la un flow anterior) și primește un access token nou fără să retrimită utilizatorul prin flow-ul de autorizare. Combinat cu rotația de refresh token, acoperă sesiunile de lungă durată fără sacrificarea securității.
Există și Implicit Grant și Resource Owner Password Credentials, ambele deprecate în OAuth 2.1. Nu le folosi în implementări noi: Implicit expunea access token-ul direct în URL, iar ROPC cere parola utilizatorului direct aplicației tale, exact ce OAuth încearcă să evite.
Ce este PKCE și de ce este obligatoriu acum pentru clienții publici?
PKCE (Proof Key for Code Exchange, RFC 7636) rezolvă o problemă specifică a Authorization Code flow-ului în clienți publici: un alt proces sau aplicație de pe același dispozitiv poate intercepta authorization code-ul dacă cunoaște URI-ul de redirect și îl poate schimba pe un token înaintea aplicației legitime. Scenariul este real pe mobile (scheme URI personalizate interceptate de aplicații malițioase) și pe desktop (aplicații locale care ascultă pe același port).
Mecanismul în patru pași:
- Înainte să inițieze flow-ul, clientul generează un code verifier: un string aleator de 43-128 caractere.
- Calculează code challenge:
BASE64URL(SHA256(code_verifier)). - Trimite
code_challengeșicode_challenge_method=S256la authorization server în cererea inițială. - La schimbul codului pe token, trimite
code_verifieroriginal. Serverul verifică căSHA256(code_verifier)==code_challengememorat.
Un atacator care a interceptat codul nu are code_verifier-ul, generat local și niciodată transmis în primul pas. Cererea lui de schimb eșuează.
OAuth 2.1 extinde obligativitatea PKCE și la clienții confidențiali. PKCE protejează împotriva interceptării codului independent de tipul clientului, fără cost de implementare semnificativ. Dacă începi o implementare nouă de Authorization Code flow, activează PKCE indiferent dacă clientul este public sau confidențial.
Care este diferența față de JWT, și de ce am ales doar JWT pentru crawlerra-backend?
OAuth 2.0 și JWT sunt ortogonale. OAuth este un protocol care definește cum se obțin și se transmit token-uri. JWT este un format de token. Un server OAuth poate emite access token-uri ca JWT-uri, dar poate la fel de bine emite token-uri opace verificate prin introspection. JWT funcționează complet independent de OAuth, în orice scenariu de transmitere verificabilă a unor claims.
Întrebarea practică este: "am nevoie de delegare de acces și de un API gateway cu authorization server separat?". Dacă da, OAuth este răspunsul. Dacă nu, JWT direct, fără infrastructura OAuth.
Pe crawlerra-backend nu rulăm OAuth: site-ul are un singur cont administrativ, fără federație, fără identity provider extern. Folosim doar JWT cu rotație de refresh token și detectare de reuse, testat în AuthFlowIT.java1. OAuth ar fi supraingineritate pentru cazul nostru de utilizare; alegerea este intenționată, nu o omisiune. Spike-urile de erori de autentificare sunt vizibile în stack-ul de observabilitate, iar pentru practica mai largă de fiabilitate a sistemului, inclusiv cum gestionăm incidentele de auth, vezi site reliability engineering.
Care sunt capcanele frecvente în implementările OAuth 2.0?
- Parametrul
stateomis sau static.stateeste mecanismul de protecție CSRF al flow-ului. Trebuie generat aleator per sesiune, trimis la authorization server și verificat la întoarcere. Unstatestatic sau absent permite unui atacator să lege victima de contul lui (login CSRF). Este eroarea cea mai frecventă în codul OAuth scris manual. - Scope creep. Scope-uri mai largi decât necesarul imediat produc token-uri cu suprafață de atac mai mare. Un token cu
write:allfurat este o catastrofă; unul curead:emaileste o problemă controlabilă. Solicită minimum necesar, fără să ceri în avans ce "poate îți trebuie cândva". Scope-urile largi sunt și un vector de abuz al endpoint-urilor pe care le expui. - Token leakage prin header-ul Referer. Dacă access token-ul apare în URL (ca parametru de query), este locat în proxy-uri, CDN-uri, servere de analytics și în istoricul browserului. Un link de pe pagina respectivă trimite token-ul în Referer la destinație. Token-urile trebuie transmise exclusiv în header-ul
Authorization: Bearer, niciodată în URL. - Redirect URI inexact. Authorization server-ul trebuie să valideze redirect URI-ul exact, nu prin prefix sau wildcard. Un pattern
https://app.example.com/*permite redirecționarea codului lahttps://app.example.com/evil, dacă atacatorul controlează acel path. - Refresh token fără rotație și fără expirare. Un refresh token static de lungă durată (luni sau ani) devine efectiv o parolă. Dacă este furat, atacatorul menține accesul indefinit. Rotația la fiecare utilizare, combinată cu o durată maximă rezonabilă (30–90 zile), limitează fereastra de exploatare.
AuthFlowIT.javaacoperă login, utilizare access token, refresh, reuse detection și logout pentru singurul cont admin alcrawlerra-backend. Fără OAuth: nicio federație, niciun identity provider extern.[crawlerra.auth_flow_it]
Întrebări frecvente
OAuth 2.0 este același lucru cu autentificarea?
Nu. OAuth 2.0 este un protocol de delegare a accesului, nu de autentificare. Delegă dreptul de a acționa în numele unui utilizator. Dacă vrei autentificare, ai nevoie de OpenID Connect, care este un strat construit peste OAuth 2.0 ce adaugă un ID token cu identitatea utilizatorului.
Ce este PKCE și de ce contează pentru SPA-uri?
PKCE (Proof Key for Code Exchange, RFC 7636) este un mecanism care împiedică interceptarea authorization code-ului în clienți publici. Fără PKCE, o aplicație malițioasă instalată pe același device poate fura codul de autorizare și îl poate schimba pe un token înainte ca aplicația legitimă să o facă. PKCE face asta imposibil chiar dacă atacatorul interceptează codul.
Care este diferența dintre OAuth 2.0 și JWT?
OAuth 2.0 este un protocol; JWT este un format de token. Sunt ortogonale. Un server OAuth poate emite access token-uri ca JWT-uri, dar poate la fel de bine emite token-uri opace (un string aleator). JWT poate fi folosit complet independent de OAuth, pentru orice scenariu de transmitere verificabilă de date.
Când să nu folosesc OAuth 2.0?
Când ai un singur utilizator sau un singur cont administrativ fără nevoie de federație. OAuth adaugă complexitate reală: authorization server, management de scopes, refresh tokens cu stocare, token introspection. Dacă nu ai nevoie de acces delegat și nu integrezi identity provider-i externi, JWT simplu cu refresh rotation este mai potrivit și mai ușor de auditat.
Cum protejez refresh token-urile în OAuth?
Stochează-le HttpOnly + SameSite=Strict, rotează la fiecare folosire și implementează reuse detection. Un refresh token furat dintr-un cookie HttpOnly necesită XSS, nu simpla inspectare a localStorage. Rotația înseamnă că fiecare refresh produce un token nou și îl invalidează pe cel vechi. Reuse detection înseamnă că dacă același token este folosit de două ori, ambele sesiuni sunt deconectate imediat.