Application Security

Sécuriser le Web moderne : Implémenter OAuth2 PKCE pour les SPAs et les clients mobiles

Pendant des années, le flux de code d'autorisation était la référence en matière de sécurisation des applications web. Cependant, les applications monopages (SPA) et les clients mobiles natifs ont introduit un défi unique : ce sont des « clients publics ». Contrairement aux applications côté serveur traditionnelles, ces clients ne peuvent pas stocker de manière sécurisée un secret client. Cette vulnérabilité a ouvert la porte aux acteurs malveillants pour intercepter les codes d'autorisation et usurper l'identité des utilisateurs légitimes, une menace connue sous le nom d'interception de code d'autorisation.

La réponse de l'industrie a été l'introduction de PKCE (Proof Key for Code Exchange), défini dans la RFC 7636. PKCE ajoute une couche de sécurité sans nécessiter de secret client, ce qui en fait la norme obligatoire pour OAuth2 dans les clients publics. Cet article guidera les développeurs de niveau intermédiaire à avancé à travers les mécanismes de PKCE et la manière de l'implémenter de manière sécurisée.

Pourquoi PKCE est non négociable pour les clients publics

Avant de plonger dans le code, il est crucial de comprendre le modèle de menace. Dans un flux de code d'autorisation standard sans PKCE, le flux se présente comme suit :

  1. Le client redirige l'utilisateur vers le serveur d'autorisation.
  2. L'utilisateur s'authentifie et accorde son autorisation.
  3. Le serveur d'autorisation redirige vers le client avec un code d'autorisation.
  4. Le client échange le code contre un jeton d'accès en utilisant son ID client et son secret.

Dans une SPA ou une application mobile, il n'y a pas de backend pour détenir le secret. Si un attaquant peut intercepter le code d'autorisation (via XSS, malware ou sniffing réseau), il peut utiliser ce code pour demander un jeton d'accès, détournant ainsi efficacement la session de l'utilisateur. PKCE résout ce problème en liant le code d'autorisation à une clé cryptographique que seul le client connaît au moment de la demande initiale.

Explication du flux PKCE

Le flux PKCE introduit deux composants clés : le code_verifier et le code_challenge.

  • Code Verifier : Une chaîne aléatoire cryptographique à haute entropie générée par le client.
  • Code Challenge : Une version transformée du vérificateur, envoyée au serveur d'autorisation lors de la demande de connexion initiale.

Lorsque le client échange le code contre un jeton plus tard, il envoie le code_verifier original. Le serveur d'autorisation transforme ce vérificateur et le compare au code_challenge qu'il a reçu précédemment. S'ils correspondent, le jeton est émis.

Implémentation : Génération du Code Verifier et du Challenge

La méthode la plus sécurisée pour générer le challenge consiste à utiliser la transformation S256. Cela implique de prendre le hachage SHA-256 du code verifier et d'encoder le résultat en Base64 URL-safe.

Voici une implémentation pratique en JavaScript pour générer ces valeurs dans un environnement de navigateur :

function generateRandomString(length) {
  const array = new Uint32Array(length / 2);
  window.crypto.getRandomValues(array);
  return Array.from(array, dec => ('0' + dec.toString(16)).slice(-2)).join('');
}

async function createVerifier() {
  // Génère une chaîne aléatoire entre 43 et 128 caractères
  const verifier = generateRandomString(32);
  return verifier;
}

async function createChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const hash = await window.crypto.subtle.digest('SHA-256', data);
  
  // Conversion en chaîne encodée Base64 URL
  const base64encoded = btoa(String.fromCharCode(...new Uint8Array(hash)));
  return base64encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}

Exécution de la demande d'autorisation

Une fois que vous avez votre code_verifier et code_challenge, vous devez rediriger l'utilisateur vers le serveur d'autorisation. L'ajout critique ici est les paramètres code_challenge et code_challenge_method=S256.

const params = new URLSearchParams({
  client_id: 'YOUR_CLIENT_ID',
  redirect_uri: 'http://localhost:3000/callback',
  response_type: 'code',
  scope: 'openid profile email',
  code_challenge: codeChallenge,
  code_challenge_method: 'S256'
});

window.location.href = `https://auth-server.com/authorize?${params}`;

Gestion de l'échange de jeton

Après que l'utilisateur a accordé l'accès, il est redirigé vers votre redirect_uri avec un code d'autorisation dans la chaîne de requête de l'URL. La dernière étape consiste à échanger ce code contre un jeton d'accès. Crucialement, vous devez envoyer le code_verifier que vous avez généré au début du flux.

const tokenResponse = await fetch('https://auth-server.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: authorizationCode, // Depuis les paramètres de requête de l'URL
    redirect_uri: 'http://localhost:3000/callback',
    client_id: 'YOUR_CLIENT_ID',
    code_verifier: codeVerifier // La chaîne aléatoire originale
  })
});

const tokens = await tokenResponse.json();

Conclusion

L'implémentation de PKCE n'est plus une option pour les développeurs construisant des SPAs ou des applications mobiles. En ajoutant quelques lignes de logique cryptographique, vous protégez vos utilisateurs contre les attaques d'interception de code d'autorisation. Bien que la configuration initiale nécessite une gestion minutieuse de l'état et un stockage sécurisé du code_verifier, les avantages à long terme pour la sécurité de l'application sont substantiels. À mesure que l'écosystème évolue vers des normes de sécurité plus strictes, l'adoption de PKCE garantit que vos applications restent robustes, conformes et dignes de confiance par les utilisateurs.

Share: