OAuth 2.x permet à une application d’accéder à vos données sans connaître votre mot de passe. C’est le standard utilisé quand une app demande “Se connecter avec Google” ou quand vos microservices communiquent entre eux. OAuth ne gère pas l’identité (c’est le rôle d’OIDC) — il gère la délégation d’accès : “cette application peut lire mes fichiers, mais pas les supprimer”.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »Ce guide vous explique OAuth 2.x — le standard dominant pour sécuriser l’accès aux APIs. À la fin, vous saurez :
- Identifier les 4 acteurs OAuth et leur rôle
- Choisir le bon flow selon votre contexte (SPA, mobile, server-to-server)
- Implémenter PKCE correctement (obligatoire depuis OAuth 2.1)
- Gérer les tokens : durées de vie, rotation, révocation, introspection
Prérequis : avoir lu Les bases de l’IAM.
Le problème qu’OAuth résout
Section intitulée « Le problème qu’OAuth résout »Imaginez : vous voulez qu’une application de suivi de commits accède à vos repositories GitHub. Avant OAuth, vous auriez dû :
- Donner votre mot de passe GitHub à l’application
- Faire confiance à cette application pour ne pas l’utiliser malicieusement
- Changer votre mot de passe pour révoquer l’accès (révoquant aussi tous les autres accès)
OAuth permet de déléguer un accès limité sans partager le mot de passe :
- L’application obtient un token avec des permissions spécifiques (scopes)
- Vous pouvez révoquer ce token sans toucher à votre mot de passe
- L’application ne voit jamais vos credentials
Les 4 acteurs OAuth
Section intitulée « Les 4 acteurs OAuth »| Acteur | Rôle | Exemple |
|---|---|---|
| Resource Owner | L’entité qui possède les données | Vous (votre compte GitHub) |
| Client | L’application qui veut accéder aux données | Application de suivi de commits |
| Authorization Server | Authentifie le Resource Owner, délivre les tokens | Keycloak, Auth0, GitHub OAuth |
| Resource Server | API qui protège les données, valide les tokens | API GitHub |
Schéma des interactions
Section intitulée « Schéma des interactions »Scopes : les permissions demandées
Section intitulée « Scopes : les permissions demandées »Les scopes définissent quelles permissions le client demande. L’utilisateur (Resource Owner) peut les accepter ou les refuser.
Exemples de scopes
Section intitulée « Exemples de scopes »| Service | Scope | Permission accordée |
|---|---|---|
| GitHub | repo:read | Lire les repositories |
| GitHub | repo:write | Modifier les repositories |
profile | Lire le profil public | |
email | Lire l’adresse email | |
| Keycloak | offline_access | Obtenir un refresh token |
Principe du moindre privilège
Section intitulée « Principe du moindre privilège »Demandez uniquement les scopes nécessaires. Plus vous demandez de permissions, plus l’utilisateur hésitera à les accorder.
Redirect URI et le flux de redirection
Section intitulée « Redirect URI et le flux de redirection »Le Redirect URI (ou callback URL) est l’URL vers laquelle l’Authorization Server redirige l’utilisateur après l’authentification.
Sécurité critique
Section intitulée « Sécurité critique »| Règle | Pourquoi |
|---|---|
| Exact match obligatoire | Évite l’interception du code/token par un attaquant |
| Enregistré à l’avance | Le serveur ne redirige que vers les URIs connues |
| HTTPS obligatoire | Sauf http://localhost pour le développement |
Les flows OAuth : lequel choisir ?
Section intitulée « Les flows OAuth : lequel choisir ? »OAuth 2.0 définit plusieurs “flows” (ou “grants”). OAuth 2.1 en conserve seulement les sécurisés.
Le flow recommandé pour tout client interactif
Section intitulée « Le flow recommandé pour tout client interactif »Usage : SPA, mobile, applications web avec utilisateur
Pourquoi PKCE ? Protège contre l’interception du code d’autorisation.
-
Génération du verifier
Le client génère un
code_verifieraléatoire (32 octets, base64url). -
Calcul du challenge
Le client calcule
code_challenge = SHA256(code_verifier). -
Requête d’autorisation
Le client redirige vers l’Authorization Server avec le
code_challenge. -
Authentification + consentement
L’utilisateur s’authentifie et accepte les scopes.
-
Retour avec le code
L’Authorization Server redirige vers le client avec un
authorization_code. -
Échange du code
Le client envoie le
code+code_verifieret reçoit les tokens.
// Étape 1-2 : Génération PKCEconst codeVerifier = crypto.randomBytes(32).toString('base64url');const codeChallenge = crypto .createHash('sha256') .update(codeVerifier) .digest('base64url');
// Étape 3 : Requête d'autorisationconst authUrl = new URL('https://auth.example.com/authorize');authUrl.searchParams.set('response_type', 'code');authUrl.searchParams.set('client_id', 'my-app');authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');authUrl.searchParams.set('scope', 'openid profile email');authUrl.searchParams.set('state', generateRandomState()); // Anti-CSRFauthUrl.searchParams.set('code_challenge', codeChallenge);authUrl.searchParams.set('code_challenge_method', 'S256');
// Rediriger l'utilisateur vers authUrl// Étape 6 : Échange du code côté callbackconst tokenResponse = await fetch('https://auth.example.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authorizationCode, redirect_uri: 'https://myapp.com/callback', client_id: 'my-app', code_verifier: codeVerifier, // Prouve qu'on est l'initiateur }),});
const { access_token, refresh_token, id_token } = await tokenResponse.json();Pour le machine-to-machine (pas d’utilisateur)
Section intitulée « Pour le machine-to-machine (pas d’utilisateur) »Usage : microservices, jobs batch, APIs internes
# Le client s'authentifie directement avec ses credentialscurl -X POST https://auth.example.com/token \ -d "grant_type=client_credentials" \ -d "client_id=api-backend" \ -d "client_secret=secret" \ -d "scope=read:orders"Réponse :
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600}À ne plus utiliser
Section intitulée « À ne plus utiliser »| Flow | Problème | Alternative |
|---|---|---|
| Implicit | Token exposé dans l’URL (fragment), pas de refresh token | Authorization Code + PKCE |
| Resource Owner Password (ROPC) | L’application voit le mot de passe | Authorization Code + PKCE |
Paramètres de sécurité obligatoires
Section intitulée « Paramètres de sécurité obligatoires »| Paramètre | Rôle | Quand l’utiliser |
|---|---|---|
state | Anti-CSRF — lie la requête à la session | Toujours |
code_challenge | PKCE — protège contre l’interception | Toujours (OAuth 2.1) |
nonce | Anti-replay pour OIDC | Avec OIDC |
Validation du state
Section intitulée « Validation du state »// Avant la redirectionconst state = crypto.randomBytes(16).toString('hex');sessionStorage.setItem('oauth_state', state);
// Au retour du callbackconst returnedState = new URLSearchParams(location.search).get('state');const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) { throw new Error('State mismatch - possible CSRF attack');}Gestion des tokens
Section intitulée « Gestion des tokens »Access Token vs Refresh Token
Section intitulée « Access Token vs Refresh Token »| Token | Durée de vie | Usage | Stockage |
|---|---|---|---|
| Access Token | Court (5-60 min) | Appels API | Mémoire (SPA), secure storage (mobile) |
| Refresh Token | Long (heures/jours) | Obtenir un nouveau access token | Cookie HttpOnly, secure storage |
Refresh : obtenir un nouveau token
Section intitulée « Refresh : obtenir un nouveau token »const refreshResponse = await fetch('https://auth.example.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: currentRefreshToken, client_id: 'my-app', }),});
const { access_token, refresh_token } = await refreshResponse.json();// Attention : refresh_token peut être nouveau (rotation)Rotation des refresh tokens
Section intitulée « Rotation des refresh tokens »La rotation émet un nouveau refresh token à chaque utilisation. Si un token est volé et utilisé :
- L’attaquant obtient un nouveau token
- La victime utilise l’ancien token (invalide)
- Le serveur détecte l’anomalie et révoque toute la chaîne
Révocation
Section intitulée « Révocation »# Révoquer un token (côté client)curl -X POST https://auth.example.com/revoke \ -d "token=eyJhbGciOiJSUzI1NiIs..." \ -d "client_id=my-app"Introspection (tokens opaques)
Section intitulée « Introspection (tokens opaques) »Pour les tokens opaques (non-JWT), le Resource Server doit les valider auprès de l’Authorization Server :
curl -X POST https://auth.example.com/introspect \ -u api-resource-server:secret \ -d "token=abc123opaque"Réponse :
{ "active": true, "sub": "user-123", "scope": "read:orders", "exp": 1700000000}Vulnérabilités classiques
Section intitulée « Vulnérabilités classiques »| Vulnérabilité | Cause | Protection |
|---|---|---|
| Authorization code interception | Pas de PKCE | PKCE obligatoire |
| Open redirect | Redirect URI avec wildcard | Exact match |
| Token leak via referer | Token dans l’URL | Ne pas mettre de token en query string |
| CSRF | Pas de state | Toujours envoyer et valider state |
| Token trop long | Durée de vie excessive | Access token < 1h, refresh token avec rotation |
| Scope trop large | Permissions excessives | Moindre privilège |
OAuth 2.0 vs OAuth 2.1
Section intitulée « OAuth 2.0 vs OAuth 2.1 »OAuth 2.1 consolide les bonnes pratiques de sécurité en un seul document.
| Changement | OAuth 2.0 | OAuth 2.1 |
|---|---|---|
| PKCE | Optionnel | Obligatoire pour tous les clients |
| Implicit flow | Disponible | Supprimé |
| ROPC | Disponible | Supprimé |
| Redirect URI | Matching flexible possible | Exact match obligatoire |
| Refresh token rotation | Non spécifié | Recommandé pour public clients |
Erreurs fréquentes
Section intitulée « Erreurs fréquentes »| Erreur | Conséquence | Solution |
|---|---|---|
| Pas de PKCE sur client public | Vulnérable à l’interception | PKCE obligatoire |
client_secret côté client (SPA) | Secret exposé | Client public sans secret, utiliser PKCE |
| Refresh token stocké en localStorage | XSS = vol | Cookie HttpOnly ou mémoire + refresh silencieux |
Pas de validation du state | CSRF possible | Générer et valider un state aléatoire |
| Access token de 24h | 24h d’accès post-compromission | Token courts (15-60 min) + refresh |
À retenir
Section intitulée « À retenir »Références
Section intitulée « Références »- RFC 6749 — OAuth 2.0 Authorization Framework
- RFC 7636 — PKCE
- OAuth 2.1 Draft
- OAuth 2.0 Security Best Current Practice