Authentification et sécurité web - 03 - OAuth2 : les flows expliques sans jargon

OAuth2 demystifie : Authorization Code, PKCE, Client Credentials. Les flows expliques avec des schemas concrets.

03 - OAuth2 : les flows expliques sans jargon

Ce que tu vas apprendre

  • Ce qu'OAuth2 est (et ce qu'il n'est pas)
  • Les trois flows principaux et quand utiliser chacun
  • La différence entre OAuth2 et OpenID Connect

Prerequisites

Avoir lu les articles sur les sessions et les JWT. Comprendre les bases des requêtes HTTP (GET, POST, redirections).


OAuth2 n'est pas un système de login

La première confusion a eliminer : OAuth2 est un protocole d'autorisation, pas d'authentification. Il répond a la question "est-ce que cette application a le droit d'acceder a mes donnees ?" et pas "qui suis-je ?".

L'exemple classique : tu utilises une app de gestion de projet qui veut acceder a tes repos GitHub. Tu ne donnes pas ton mot de passe GitHub a cette app. A la place, GitHub te demande "est-ce que tu autorises cette app a lire tes repos ?" et si tu dis oui, l'app recoit un token d'acces avec des permissions limitees.

Pour l'authentification ("Login with Google"), c'est OpenID Connect (OIDC) qui se construit par-dessus OAuth2. Mais dans la pratique, quand les gens disent "OAuth2 login", ils parlent d'OIDC. La spec OAuth2 (RFC 6749) ne mentionne meme pas le concept d'identité utilisateur.

Flow 1 : Authorization Code (apps avec backend)

C'est le flow le plus courant et le plus sécurisé. Il est fait pour les applications web qui ont un serveur backend.

Utilisateur          Ton App (frontend)       Ton Serveur          Google
    |                      |                       |                  |
    |-- Clic "Login" ----->|                       |                  |
    |                      |-- Redirect ---------->|                  |
    |                      |                       |-- Redirect ----->|
    |<--------------------- Page de login Google --|                  |
    |-- Accepte ---------->|                       |                  |
    |                      |<-- Redirect avec ?code=abc123 ---------|
    |                      |-- Envoie code ------->|                  |
    |                      |                       |-- POST /token -->|
    |                      |                       |   (code + secret)|
    |                      |                       |<-- access_token -|
    |                      |<-- Session cookie ----|                  |

En TypeScript, l'échange du code contre un token ressemble a ca :

typescript// Etape 4 : ton backend echange le code contre un access token
const response = await fetch("https://oauth2.googleapis.com/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    code: authCode,           // le code recu dans l'URL de callback
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,  // jamais expose au frontend
    redirect_uri: "https://monapp.com/callback",
    grant_type: "authorization_code",
  }),
});

const { access_token, refresh_token } = await response.json();

Le point important : le client_secret ne quitte jamais ton serveur. Il n'apparaît dans aucune requête cote navigateur. C'est pour ca que ce flow nécessité un backend.

Flow 2 : Authorization Code + PKCE (SPAs et mobile)

Les Single Page Applications et les apps mobiles n'ont pas de backend pour garder un secret. Le code source est accessible (F12 dans le navigateur, decompilation sur mobile). Le client_secret ne peut pas etre secret.

PKCE (prononce "pixy", pour Proof Key for Code Exchange, RFC 7636) resout ce problème. L'idee : au lieu d'un secret statique, on généré un secret a usage unique pour chaque requête.

typescript// 1. Generer un code_verifier (chaine aleatoire)
const codeVerifier = crypto.randomUUID() + crypto.randomUUID();

// 2. En deriver un code_challenge (hash SHA-256)
const encoder = new TextEncoder();
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
  .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

// 3. Envoyer le code_challenge dans la requete d'autorisation
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
  `client_id=${CLIENT_ID}&` +
  `redirect_uri=${REDIRECT_URI}&` +
  `response_type=code&` +
  `code_challenge=${codeChallenge}&` +
  `code_challenge_method=S256`;

// 4. Echanger le code avec le code_verifier (pas de client_secret)
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
  method: "POST",
  body: new URLSearchParams({
    code: authCode,
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    grant_type: "authorization_code",
    code_verifier: codeVerifier,  // le serveur Google verifie que SHA-256(codeVerifier) == codeChallenge
  }),
});

Meme si un attaquant intercepte le code dans l'URL de callback, il ne peut pas l'échanger sans le code_verifier qui n'a jamais transite par le réseau.

Flow 3 : Client Credentials (machine a machine)

Pas d'utilisateur dans la boucle. Ton serveur s'authentifié directement auprès d'un autre service.

typescript// Ton serveur backend veut appeler l'API d'un partenaire
const response = await fetch("https://api.partenaire.com/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET,
    grant_type: "client_credentials",
    scope: "read:data",
  }),
});

const { access_token } = await response.json();
// Utilise access_token pour appeler l'API

C'est le flow le plus simple. Pas de redirection, pas de navigateur. Service A donne ses identifiants a Service B, recoit un token, et l'utilise. La gestion du client_secret est le meme problème que pour les secrets et variables d'environnement.

Access tokens vs refresh tokens

L'access token a une duree de vie courte (1 heure typiquement). Le refresh token permet d'en obtenir un nouveau sans redemander a l'utilisateur. On en parle en détail dans l'article suivant.

Mon conseil

Si tu implementes un "Login with Google/GitHub/Microsoft" dans ton app, utilise une librairie. Arctic est minimaliste et bien faite. Lucia gere aussi les sessions. NextAuth.js si tu es sur Next.js.

Implementer OAuth2 from scratch, c'est pedagogique (je te recommande de le faire une fois pour comprendre). Mais en production, il y a trop de cas limites et de validations de sécurité pour tout gerer a la main. Sur paltemps.fr, je n'ai pas OAuth2 parce qu'il n'y a qu'un admin avec un mot de passe. Pas besoin de sortir l'artillerie lourde.


Navigation : Precedent : 02 - JWT | Suivant : 04 - Refresh tokens


Sources

Retrouve d'autres articles techniques sur paltemps.fr.

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.