Aller au contenu
medium

OAuth 2.x : la délégation d'accès aux APIs

12 min de lecture

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 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.

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
ActeurRôleExemple
Resource OwnerL’entité qui possède les donnéesVous (votre compte GitHub)
ClientL’application qui veut accéder aux donnéesApplication de suivi de commits
Authorization ServerAuthentifie le Resource Owner, délivre les tokensKeycloak, Auth0, GitHub OAuth
Resource ServerAPI qui protège les données, valide les tokensAPI GitHub

Les 4 acteurs OAuth 2.0 : Resource Owner, Authorization Server, Client et Resource Server avec leurs interactions

Les scopes définissent quelles permissions le client demande. L’utilisateur (Resource Owner) peut les accepter ou les refuser.

ServiceScopePermission accordée
GitHubrepo:readLire les repositories
GitHubrepo:writeModifier les repositories
GoogleprofileLire le profil public
GoogleemailLire l’adresse email
Keycloakoffline_accessObtenir un refresh token

Demandez uniquement les scopes nécessaires. Plus vous demandez de permissions, plus l’utilisateur hésitera à les accorder.

Le Redirect URI (ou callback URL) est l’URL vers laquelle l’Authorization Server redirige l’utilisateur après l’authentification.

RèglePourquoi
Exact match obligatoireÉvite l’interception du code/token par un attaquant
Enregistré à l’avanceLe serveur ne redirige que vers les URIs connues
HTTPS obligatoireSauf http://localhost pour le développement

OAuth 2.0 définit plusieurs “flows” (ou “grants”). OAuth 2.1 en conserve seulement les sécurisés.

Usage : SPA, mobile, applications web avec utilisateur

Pourquoi PKCE ? Protège contre l’interception du code d’autorisation.

  1. Génération du verifier

    Le client génère un code_verifier aléatoire (32 octets, base64url).

  2. Calcul du challenge

    Le client calcule code_challenge = SHA256(code_verifier).

  3. Requête d’autorisation

    Le client redirige vers l’Authorization Server avec le code_challenge.

  4. Authentification + consentement

    L’utilisateur s’authentifie et accepte les scopes.

  5. Retour avec le code

    L’Authorization Server redirige vers le client avec un authorization_code.

  6. Échange du code

    Le client envoie le code + code_verifier et reçoit les tokens.

// Étape 1-2 : Génération PKCE
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
// Étape 3 : Requête d'autorisation
const 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-CSRF
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
// Rediriger l'utilisateur vers authUrl
// Étape 6 : Échange du code côté callback
const 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();
ParamètreRôleQuand l’utiliser
stateAnti-CSRF — lie la requête à la sessionToujours
code_challengePKCE — protège contre l’interceptionToujours (OAuth 2.1)
nonceAnti-replay pour OIDCAvec OIDC
// Avant la redirection
const state = crypto.randomBytes(16).toString('hex');
sessionStorage.setItem('oauth_state', state);
// Au retour du callback
const returnedState = new URLSearchParams(location.search).get('state');
const savedState = sessionStorage.getItem('oauth_state');
if (returnedState !== savedState) {
throw new Error('State mismatch - possible CSRF attack');
}
TokenDurée de vieUsageStockage
Access TokenCourt (5-60 min)Appels APIMémoire (SPA), secure storage (mobile)
Refresh TokenLong (heures/jours)Obtenir un nouveau access tokenCookie HttpOnly, secure storage
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)

La rotation émet un nouveau refresh token à chaque utilisation. Si un token est volé et utilisé :

  1. L’attaquant obtient un nouveau token
  2. La victime utilise l’ancien token (invalide)
  3. Le serveur détecte l’anomalie et révoque toute la chaîne
Fenêtre de terminal
# Révoquer un token (côté client)
curl -X POST https://auth.example.com/revoke \
-d "token=eyJhbGciOiJSUzI1NiIs..." \
-d "client_id=my-app"

Pour les tokens opaques (non-JWT), le Resource Server doit les valider auprès de l’Authorization Server :

Fenêtre de terminal
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éCauseProtection
Authorization code interceptionPas de PKCEPKCE obligatoire
Open redirectRedirect URI avec wildcardExact match
Token leak via refererToken dans l’URLNe pas mettre de token en query string
CSRFPas de stateToujours envoyer et valider state
Token trop longDurée de vie excessiveAccess token < 1h, refresh token avec rotation
Scope trop largePermissions excessivesMoindre privilège

OAuth 2.1 consolide les bonnes pratiques de sécurité en un seul document.

ChangementOAuth 2.0OAuth 2.1
PKCEOptionnelObligatoire pour tous les clients
Implicit flowDisponibleSupprimé
ROPCDisponibleSupprimé
Redirect URIMatching flexible possibleExact match obligatoire
Refresh token rotationNon spécifiéRecommandé pour public clients
ErreurConséquenceSolution
Pas de PKCE sur client publicVulnérable à l’interceptionPKCE obligatoire
client_secret côté client (SPA)Secret exposéClient public sans secret, utiliser PKCE
Refresh token stocké en localStorageXSS = volCookie HttpOnly ou mémoire + refresh silencieux
Pas de validation du stateCSRF possibleGénérer et valider un state aléatoire
Access token de 24h24h d’accès post-compromissionToken courts (15-60 min) + refresh

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.