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 claimssub (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_verifier alé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 tokenRefresh tokenID token (OIDC)
Sert àAppeler l’APIObtenir de nouveaux access tokensSavoir qui est connecté
Consommé parResource serverAuthorization serverLe client
Durée de vieCourte (5-15 min)Longue (jours), révocableCourte, lu à la connexion
Stockage typeMémoire JSCookie httpOnly, avec rotationPas 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, iss et aud. 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 : exp se 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 localStorage est lisible par n’importe quel script de la page : une faille XSS (ou un paquet npm compromis) suffit à l’exfiltrer. Un cookie httpOnly + Secure + SameSite est invisible pour JS, mais envoyé automatiquement → surface CSRF (atténuée par SameSite=Lax/Strict et 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: none et 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

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 claimssub (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 tokenRefresh tokenID token (OIDC)
PurposeCall the APIObtain new access tokensKnow who is logged in
Consumed byResource serverAuthorization serverThe client
LifetimeShort (5-15 min)Long (days), revocableShort, read at login
Typical storageJS memoryhttpOnly cookie, rotatedNot 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, iss and aud. 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: exp is 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 kid identifier; keys rotate without redeploying the services.

⚠️ localStorage vs httpOnly cookie — a token in localStorage is readable by any script on the page: one XSS flaw (or one compromised npm package) is enough to exfiltrate it. An httpOnly + Secure + SameSite cookie is invisible to JS, but sent automatically → CSRF surface (mitigated by SameSite=Lax/Strict and 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: none and 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

S'entraîner sur ce sujet → Practice this topic →