Jour 5 Day 5 · vendredi 31 juillet 2026 Friday 31 July 2026 Sécurité Intermédiaire
OAuth 2.0, OIDC & JWT OAuth 2.0, OIDC & JWT
Qui es-tu, qu'as-tu le droit de faire, et comment le prouver sans état côté serveur : le trio auth le plus demandé en entretien backend — et celui où les candidats mélangent tout. Who you are, what you may do, and how to prove it without server-side state: the auth trio most asked about in backend interviews — and the one candidates mix up the most.
L’essentiel
Authentification : prouver qui on est. Autorisation : décider ce qu’on a le droit de faire. Toute la confusion autour du sujet vient de là : OAuth 2.0 est un framework d’autorisation déléguée (« cette app peut lire mes repos GitHub sans connaître mon mot de passe »), pas un protocole d’authentification. C’est OIDC (OpenID Connect) qui ajoute la couche d’identité par-dessus. Et JWT (JSON Web Token) n’est qu’un format de token signé, utilisé (entre autres) par ces protocoles.
Deux grands modèles pour maintenir un utilisateur connecté :
- Sessions + cookies : le serveur garde l’état (mémoire, Redis, DB) ; le navigateur ne détient qu’un identifiant de session opaque dans un cookie. Révocation triviale (supprimer la session), mais un état à partager entre instances.
- Tokens auto-porteurs (JWT) : l’état est dans le token, signé par le serveur. Tout service qui détient la clé de vérification valide le token sans appel réseau ni stockage partagé — idéal en microservices — mais la révocation devient le problème.
Comment ça marche
Un JWT, ce sont trois parties encodées en base64url, séparées par des points : header.payload.signature.
- Header : l’algorithme de signature —
{"alg":"RS256","typ":"JWT"}. - Payload : les claims —
sub(sujet),iss(émetteur),aud(audience),exp(expiration),iat(émission), plus des claims métier (rôles, email). - Signature : calculée sur header + payload. HS256 = HMAC avec un secret partagé (le même secret signe et vérifie — symétrique). RS256 = RSA : la clé privée signe, la clé publique vérifie. En microservices, RS256 s’impose : les services vérifient avec la clé publique (publiée via un endpoint JWKS) sans jamais détenir la clé privée.
Décodé, ça donne :
// Header — 1re partie
{ "alg": "RS256", "typ": "JWT", "kid": "2026-key-1" }
// Payload — 2e partie : les claims, lisibles par tous
{
"sub": "user-42", // qui (le sujet)
"iss": "https://auth.exemple.com", // émis par qui
"aud": "api.exemple.com", // pour quelle API
"exp": 1754061600, // expire à (timestamp Unix)
"iat": 1754060700, // émis à
"roles": ["dev"] // claim métier
}
// Signature — 3e partie : signe header + payload
Point crucial : un JWT est signé, pas chiffré. Le payload ci-dessus se décode en un clic (jwt.io). La signature garantit l’intégrité et l’origine, pas la confidentialité.
💡 À faire une fois — coller un vrai token dans jwt.io et voir son payload en clair vaut tous les rappels : base64 ≠ chiffrement. Donc jamais de mot de passe ni de donnée personnelle inutile dans un JWT.
Le problème de la révocation : un JWT reste valide jusqu’à son exp — le serveur ne « voit » pas un logout ou un bannissement. La réponse standard : access token court (5-15 min) + refresh token long, stocké et révocable côté serveur. Un access token volé a une fenêtre d’exploitation courte ; le refresh token, lui, se révoque — et se rotate : chaque usage en émet un nouveau, et la réutilisation d’un ancien signale un vol.
OAuth 2.0 définit quatre rôles : resource owner (l’utilisateur), client (l’application), authorization server (émet les tokens), resource server (l’API protégée). Les flows à connaître :
- Authorization code + PKCE — le flow de référence (schéma ci-dessous). PKCE : le client génère un
code_verifieraléatoire, envoie son hash SHA-256 (code_challenge) au départ, puis prouve la possession du verifier à l’échange — un code intercepté est inexploitable. Recommandé pour tous les clients (SPA, mobile, backend), obligatoire dans OAuth 2.1. - Client credentials — machine-to-machine, aucun utilisateur : le client s’authentifie directement (batch, service interne).
- Implicit (token renvoyé dans le fragment d’URL) et ROPC (mot de passe saisi dans le client) : dépréciés, supprimés d’OAuth 2.1. À citer comme tels, pas à utiliser.
Le flow authorization code + PKCE en séquence :
Client (SPA) Authorization Server API
│ 1. redirect + code_challenge│ │
├────────────────────────────▶│ │
│ 2. login + consentement │ │
│◀───────────────────────────▶│ │
│ 3. code (éphémère) │ │
│◀────────────────────────────┤ │
│ 4. code + code_verifier │ │
├────────────────────────────▶│ │
│ 5. access + refresh │ │
│ (+ id_token si OIDC) │ │
│◀────────────────────────────┤ │
│ 6. Authorization: Bearer <access_token> │
├─────────────────────────────┴─────────────────▶│
OIDC standardise l’identité au-dessus d’OAuth : le scope openid, un id_token (un JWT) portant qui est l’utilisateur (claims standardisés : sub, email, name…), et un endpoint /userinfo. « Login with Google », c’est OIDC. Trois tokens circulent désormais — ne jamais utiliser l’un pour l’autre :
| Access token | Refresh token | ID token (OIDC) | |
|---|---|---|---|
| Sert à | Appeler l’API | Obtenir de nouveaux access tokens | Savoir qui est connecté |
| Consommé par | Resource server | Authorization server | Le client |
| Durée de vie | Courte (5-15 min) | Longue (jours), révocable | Courte, lu à la connexion |
| Stockage type | Mémoire JS | Cookie httpOnly, avec rotation | Pas stocké |
🎤 En entretien — la métaphore qui marche : l’access token est le ticket de cinéma (courte durée, donne l’accès), le refresh token la carte d’abonnement (en génère de nouveaux, révocable au guichet), l’id_token la pièce d’identité (dit qui vous êtes, ne donne accès à rien).
Concepts clés à maîtriser
- Valider un JWT côté serveur : vérifier la signature avec l’algorithme attendu (imposé par le serveur, jamais lu aveuglément dans le header), puis
exp,issetaud. Toujours via une lib éprouvée (jose, jsonwebtoken, PyJWT) — jamais de crypto maison. - Stockage côté front : le pattern robuste — access token en mémoire (variable JS, perdu au refresh, renouvelé silencieusement), refresh token en cookie httpOnly. Le pourquoi est dans l’encadré ci-dessous.
- Scopes : le périmètre demandé par le client (
repo:read) — de l’autorisation, pas de l’identité. Le resource server doit les vérifier. - Expiration et horloges :
expse vérifie côté serveur avec une petite tolérance (clock skew) ; côté client, on rafraîchit avant l’expiration. - JWKS et rotation de clés : l’authorization server publie ses clés publiques avec un identifiant
kid; on fait tourner les clés sans redéployer les services.
⚠️ localStorage vs cookie httpOnly — un token dans
localStorageest lisible par n’importe quel script de la page : une faille XSS (ou un paquet npm compromis) suffit à l’exfiltrer. Un cookiehttpOnly+Secure+SameSiteest invisible pour JS, mais envoyé automatiquement → surface CSRF (atténuée parSameSite=Lax/Strictet un token anti-CSRF). Aucune option n’est sûre « toute seule » : c’est un arbitrage, et il faut savoir le défendre.
En entretien
« Différence entre authentification et autorisation ? » — Authentification = vérifier l’identité (login, MFA). Autorisation = vérifier les droits (ce user peut-il supprimer cette ressource ?). Le bonus qui fait mouche : OAuth 2.0 fait de l’autorisation ; l’authentification, c’est le rôle d’OIDC.
« Explique la structure d’un JWT. » — Trois segments base64url : header (alg), payload (claims : sub, iss, aud, exp…), signature sur les deux premiers. Signé, pas chiffré : n’importe qui le lit, personne ne le modifie sans casser la signature. HS256 secret partagé vs RS256 clé privée/publique.
« Comment déconnecter un utilisateur si son JWT est encore valide ? » — On ne peut pas invalider le token lui-même sans réintroduire de l’état. Réponse attendue : access token court + révocation du refresh token ; si l’invalidation immédiate est exigée, denylist des jti (dans Redis) jusqu’à exp — en assumant qu’on a re-perdu le « stateless ».
« Pourquoi PKCE ? » — Historiquement pour les clients publics (mobile/SPA) incapables de garder un client_secret : le hash du code_verifier lie le code au client qui l’a demandé, donc un code volé ne s’échange pas. Aujourd’hui recommandé partout, même avec un secret.
« Où stockes-tu le token côté front ? » — Dérouler l’arbitrage XSS vs CSRF de l’encadré plus haut, puis conclure : access token en mémoire + refresh token en cookie httpOnly. Montrer qu’on connaît les deux attaques vaut plus que la réponse elle-même.
Pièges & idées reçues
alg: noneet confusion d’algorithme : de vieilles libs acceptaient un token déclarant"alg":"none"(signature vide !) ou vérifiaient un token RS256 en HS256 avec la clé publique comme secret HMAC. Parade : imposer côté serveur la liste des algorithmes acceptés.- Secret HS256 faible : la signature se brute-force hors ligne (hashcat) — un secret type « secret123 » tombe en quelques secondes, et l’attaquant forge ensuite des tokens admin. Secret long et aléatoire (256 bits), ou RS256.
- « JWT = moderne, sessions = obsolète » : pour une app web monolithe, une session server-side est plus simple ET révocable. Le JWT se justifie par le multi-services, pas par la mode.
- Oublier
aud/iss: un token émis pour le service A accepté par le service B — le claim d’audience existe précisément pour ça.
Pour aller plus loin
- RFC 6749 — The OAuth 2.0 Authorization Framework et RFC 7519 — JSON Web Token
- RFC 9700 — Best Current Practice for OAuth 2.0 Security : l’état de l’art officiel (2025)
- OpenID Connect Core et OAuth 2.1 (draft), qui consolide les bonnes pratiques
- jwt.io pour décoder des tokens, et la cheat sheet OWASP JWT côté implémentation
The essentials
Authentication: proving who you are. Authorization: deciding what you’re allowed to do. All the confusion around this topic starts there: OAuth 2.0 is a delegated authorization framework (“this app may read my GitHub repos without knowing my password”), not an authentication protocol. OIDC (OpenID Connect) is what adds the identity layer on top. And JWT (JSON Web Token) is just a signed token format, used (among others) by these protocols.
Two main models to keep a user logged in:
- Sessions + cookies: the server keeps the state (memory, Redis, DB); the browser only holds an opaque session ID in a cookie. Revocation is trivial (delete the session), but state must be shared across instances.
- Self-contained tokens (JWT): the state lives in the token itself, signed by the server. Any service holding the verification key can validate the token without a network call or shared storage — ideal for microservices — but revocation becomes the problem.
How it works
A JWT is three base64url-encoded parts separated by dots: header.payload.signature.
- Header: the signing algorithm —
{"alg":"RS256","typ":"JWT"}. - Payload: the claims —
sub(subject),iss(issuer),aud(audience),exp(expiration),iat(issued at), plus business claims (roles, email). - Signature: computed over header + payload. HS256 = HMAC with a shared secret (the same secret signs and verifies — symmetric). RS256 = RSA: the private key signs, the public key verifies. In microservices, RS256 wins: services verify with the public key (published through a JWKS endpoint) without ever holding the private key.
Decoded, it looks like this:
// Header — 1st part
{ "alg": "RS256", "typ": "JWT", "kid": "2026-key-1" }
// Payload — 2nd part: the claims, readable by anyone
{
"sub": "user-42", // who (the subject)
"iss": "https://auth.example.com", // issued by whom
"aud": "api.example.com", // for which API
"exp": 1754061600, // expires at (Unix timestamp)
"iat": 1754060700, // issued at
"roles": ["dev"] // business claim
}
// Signature — 3rd part: signs header + payload
Crucial point: a JWT is signed, not encrypted. The payload above decodes in one click (jwt.io). The signature guarantees integrity and origin, not confidentiality.
💡 Do it once — paste a real token into jwt.io and see your own payload in plain text: it beats every reminder that base64 ≠ encryption. So never put a password or unnecessary personal data in a JWT.
The revocation problem: a JWT stays valid until its exp — the server never “sees” a logout or a ban. The standard answer: a short-lived access token (5-15 min) + a long-lived refresh token, stored and revocable server-side. A stolen access token has a short exploitation window; the refresh token can be revoked — and rotated: each use issues a new one, and reuse of an old one signals theft.
OAuth 2.0 defines four roles: resource owner (the user), client (the application), authorization server (issues the tokens), resource server (the protected API). The flows to know:
- Authorization code + PKCE — the reference flow (diagram below). PKCE: the client generates a random
code_verifier, sends its SHA-256 hash (code_challenge) upfront, then proves possession of the verifier at exchange time — an intercepted code is unusable. Recommended for all clients (SPA, mobile, backend), mandatory in OAuth 2.1. - Client credentials — machine-to-machine, no user involved: the client authenticates directly (batch job, internal service).
- Implicit (token returned in the URL fragment) and ROPC (password typed into the client): deprecated, removed from OAuth 2.1. Know them to name them, not to use them.
The authorization code + PKCE flow, step by step:
Client (SPA) Authorization Server API
│ 1. redirect + code_challenge│ │
├────────────────────────────▶│ │
│ 2. login + consent │ │
│◀───────────────────────────▶│ │
│ 3. code (short-lived) │ │
│◀────────────────────────────┤ │
│ 4. code + code_verifier │ │
├────────────────────────────▶│ │
│ 5. access + refresh │ │
│ (+ id_token if OIDC) │ │
│◀────────────────────────────┤ │
│ 6. Authorization: Bearer <access_token> │
├─────────────────────────────┴─────────────────▶│
OIDC standardizes identity on top of OAuth: the openid scope, an id_token (a JWT) carrying who the user is (standardized claims: sub, email, name…), and a /userinfo endpoint. “Login with Google” is OIDC. Three tokens are now in flight — never use one for the other:
| Access token | Refresh token | ID token (OIDC) | |
|---|---|---|---|
| Purpose | Call the API | Obtain new access tokens | Know who is logged in |
| Consumed by | Resource server | Authorization server | The client |
| Lifetime | Short (5-15 min) | Long (days), revocable | Short, read at login |
| Typical storage | JS memory | httpOnly cookie, rotated | Not stored |
🎤 In an interview — the metaphor that lands: the access token is the cinema ticket (short-lived, grants entry), the refresh token the membership card (issues new tickets, revocable at the desk), the id_token the ID card (says who you are, grants access to nothing).
Key concepts to master
- Validating a JWT server-side: verify the signature with the expected algorithm (enforced by the server, never blindly read from the header), then
exp,issandaud. Always through a battle-tested library (jose, jsonwebtoken, PyJWT) — never home-made crypto. - Front-end storage: the robust pattern — access token in memory (JS variable, lost on refresh, silently renewed), refresh token in an httpOnly cookie. The why is in the callout below.
- Scopes: the perimeter requested by the client (
repo:read) — authorization, not identity. The resource server must check them. - Expiration and clocks:
expis checked server-side with a small tolerance (clock skew); on the client side, refresh before expiration. - JWKS and key rotation: the authorization server publishes its public keys with a
kididentifier; keys rotate without redeploying the services.
⚠️ localStorage vs httpOnly cookie — a token in
localStorageis readable by any script on the page: one XSS flaw (or one compromised npm package) is enough to exfiltrate it. AnhttpOnly+Secure+SameSitecookie is invisible to JS, but sent automatically → CSRF surface (mitigated bySameSite=Lax/Strictand an anti-CSRF token). Neither option is safe “on its own”: it’s a trade-off, and you must be able to defend it.
In an interview
“Difference between authentication and authorization?” — Authentication = verifying identity (login, MFA). Authorization = verifying rights (may this user delete this resource?). The bonus that lands: OAuth 2.0 does authorization; authentication is OIDC’s job.
“Explain the structure of a JWT.” — Three base64url segments: header (alg), payload (claims: sub, iss, aud, exp…), signature over the first two. Signed, not encrypted: anyone can read it, nobody can modify it without breaking the signature. HS256 shared secret vs RS256 private/public key.
“How do you log a user out if their JWT is still valid?” — You can’t invalidate the token itself without reintroducing state. Expected answer: short access token + refresh token revocation; if immediate invalidation is required, a denylist of jti values (in Redis) until exp — acknowledging you’ve traded statelessness away again.
“Why PKCE?” — Historically for public clients (mobile/SPA) unable to keep a client_secret: the code_verifier hash binds the code to the client that requested it, so a stolen code cannot be exchanged. Now recommended everywhere, even with a secret.
“Where do you store the token on the front end?” — Walk through the XSS vs CSRF trade-off from the callout above, then conclude: access token in memory + refresh token in an httpOnly cookie. Showing you know both attacks is worth more than the answer itself.
Pitfalls & misconceptions
alg: noneand algorithm confusion: old libraries accepted a token declaring"alg":"none"(empty signature!) or verified an RS256 token as HS256 using the public key as the HMAC secret. Defense: enforce the list of accepted algorithms server-side.- Weak HS256 secret: the signature can be brute-forced offline (hashcat) — a “secret123”-grade secret falls in seconds, and the attacker then forges admin tokens. Long random secret (256 bits), or RS256.
- “JWT = modern, sessions = obsolete”: for a monolithic web app, a server-side session is simpler AND revocable. JWT is justified by multiple services, not by fashion.
- Forgetting
aud/iss: a token issued for service A accepted by service B — the audience claim exists precisely for that.
Going further
- RFC 6749 — The OAuth 2.0 Authorization Framework and RFC 7519 — JSON Web Token
- RFC 9700 — Best Current Practice for OAuth 2.0 Security: the official state of the art (2025)
- OpenID Connect Core and OAuth 2.1 (draft), which consolidates best practices
- jwt.io to decode tokens, and the OWASP JWT cheat sheet on the implementation side