Aller au contenu

Pièges OAuth 2.1 : PKCE, redirections et scopes

OAuth 2.1 est un cadre de délégation d’autorisation : il permet à une application d’obtenir un droit limité sur une ressource sans recevoir le mot de passe de la personne concernée. Le traiter comme un protocole d’authentification produit des prises de contrôle de compte, des jetons surpuissants et des contrôles d’accès fondés sur des données qui ne prouvent rien.

→ Lire l’explication complète de la marmotte

OAuth autorise un accès, il n’authentifie pas une personne

Section intitulée « OAuth autorise un accès, il n’authentifie pas une personne »

OAuth décrit la délégation d’un accès entre quatre rôles : le propriétaire de la ressource, le client, le serveur d’autorisation et le serveur de ressources. Le client reçoit un jeton d’accès, puis le présente au serveur de ressources. Ce jeton répond à une question étroite : « ce client peut-il appeler cette ressource avec ce périmètre ? ». Il ne répond pas, à lui seul, à « qui est connecté ? ».

Un jeton d’accès peut être opaque, donc incompréhensible hors du serveur qui l’émet, ou prendre la forme d’un JSON Web Token (JWT). Dans les deux cas, sa présence ne doit jamais être interprétée comme une session utilisateur universelle. Son audience, son émetteur, sa durée de vie, son périmètre et son contexte de délivrance déterminent sa valeur. Accepter un jeton prévu pour une API de facturation sur une API d’administration, c’est transformer un contrôle d’audience en décoration murale.

L’authentification fédérée relève d’OpenID Connect (OIDC), une couche d’identité construite au-dessus d’OAuth. OIDC ajoute notamment un jeton d’identité, destiné au client, avec des garanties sur l’émetteur et l’identité authentifiée. Même ce jeton ne doit pas être transmis à une API comme preuve d’autorisation. Le jeton d’identité est consommé par le client, le jeton d’accès par le serveur de ressources. Mélanger les deux casse la séparation des responsabilités, puis finit dans un rapport d’incident avec beaucoup de flèches rouges.

Les incidents réels issus de cette confusion suivent un schéma banal : une application accepte n’importe quel jeton signé par le fournisseur d’identité, extrait un identifiant de sujet, puis crée une session locale. L’application oublie de vérifier l’audience, le client destinataire, le flux utilisé ou les droits accordés. Un jeton obtenu légitimement pour une autre application devient alors un passe-partout. La correction n’est pas de multiplier les champs dans le jeton : il faut vérifier que chaque artefact est consommé par son destinataire prévu.

Le compromis central est net : OAuth réduit l’exposition des mots de passe et permet des permissions limitées, mais impose une architecture où les droits sont modélisés, vérifiés et révocables. La recommandation est donc de réserver OAuth à la délégation vers des interfaces de programmation, et d’utiliser OIDC quand le client doit établir une session humaine. Pour une cartographie plus large des mécanismes d’identité, consulter les fondations d’identité.

OAuth 2.1 élimine les flux qui laissaient trop de marge

Section intitulée « OAuth 2.1 élimine les flux qui laissaient trop de marge »

OAuth 2.1 consolide des pratiques de sécurité déjà publiées pour OAuth 2.0. Il ne répare pas une mauvaise politique d’autorisation et ne transforme pas une API en forteresse parce qu’un logo de fournisseur d’identité apparaît sur la page de connexion.

Le grant implicite disparaît. Ce flux renvoyait directement un jeton d’accès dans l’URL de redirection, souvent dans le fragment. Ce choix exposait le jeton aux historiques, aux scripts de page, aux journaux mal configurés et aux erreurs de manipulation côté navigateur. Le flux de code d’autorisation devient la voie normale pour les applications interactives : le navigateur reçoit un code court, le client échange ce code contre des jetons sur un canal direct.

Proof Key for Code Exchange (PKCE) devient obligatoire pour les clients publics, et doit être imposé aussi aux clients confidentiels. Un client public ne peut pas conserver un secret : application mobile, application de bureau ou interface exécutée dans un navigateur. L’idée qu’un client_secret compilé dans une application mobile reste secret survit surtout dans les présentations commerciales et les dépôts Git publics.

Le Resource Owner Password Credentials grant, souvent appelé grant de mot de passe, est exclu. Il demandait au client de collecter le mot de passe de l’utilisateur pour le remettre au serveur d’autorisation. Cela détruit la séparation qui justifie OAuth : le client connaît alors le secret principal, ne permet pas correctement l’authentification multifacteur et contourne les politiques de connexion. Une migration vers le code d’autorisation avec PKCE est obligatoire, pas facultative.

Avant déploiement, prévoir un serveur d’autorisation supportant le profil 2.1, une liste enregistrée de redirections exactes, une gestion de session côté client et un serveur de ressources capable de contrôler signature ou introspection, audience, émetteur, expiration et portée. La compatibilité OAuth 2.0 annoncée par un produit ne prouve aucun de ces points.

Redirections OAuth : le code d’autorisation ne doit jamais voyager librement

Section intitulée « Redirections OAuth : le code d’autorisation ne doit jamais voyager librement »

La redirection est la frontière la plus attaquée du protocole. Le serveur d’autorisation doit comparer l’adresse de retour reçue avec une adresse préenregistrée, après normalisation sûre, et exiger une correspondance exacte. Accepter un préfixe, un suffixe, un joker de domaine, une URL avec paramètres libres ou une sous-chaîne produit un open redirect.

L’attaque est directe : un attaquant lance une autorisation avec une redirection contrôlée, ou exploite une redirection ouverte déjà présente chez le client. Après l’authentification, le code d’autorisation arrive sur cette adresse. Sans PKCE, l’attaquant échange le code contre un jeton. Avec PKCE, le vol du code seul ne suffit pas, ce qui limite fortement l’impact, mais ne dispense pas de corriger la redirection. Une redirection ouverte reste un outil de hameçonnage et peut servir à contourner d’autres contrôles de navigation.

Le paramètre state lie la réponse au navigateur qui a initié la demande et protège contre la falsification de requête intersite. Il doit être imprévisible, lié à une session locale, à usage unique et vérifié avant toute création de session. Ne jamais y mettre un rôle, une adresse de retour arbitraire ou un objet encodé sans intégrité. Base64 n’est pas une signature, même avec une police monospace très rassurante.

Les redirections de boucle locale pour les applications natives demandent une règle spécifique : réserver un port éphémère et vérifier l’état reçu. Les schémas d’URL personnalisés sont plus exposés à l’interception par une autre application. Lorsqu’une plateforme propose un mécanisme de liaison vérifiée entre application et domaine, il doit être préféré.

PKCE lie le code au client qui a commencé le flux

Section intitulée « PKCE lie le code au client qui a commencé le flux »

PKCE repose sur une valeur secrète temporaire, le code_verifier, générée avec une entropie suffisante par le client. Le client envoie au départ une empreinte dérivée, le code_challenge, en utilisant S256 avec SHA-256 (Secure Hash Algorithm 256). Lors de l’échange du code, il transmet le vérificateur. Le serveur compare alors la valeur attendue.

Le code_verifier doit rester dans un stockage local associé à la transaction et disparaître après succès, erreur ou expiration. Le placer dans une URL, un journal applicatif, un cookie partagé entre sous-domaines ou un stockage accessible à des scripts non maîtrisés annule son intérêt. La méthode plain ne doit pas être activée : elle existe pour compatibilité historique, pas pour être redécouverte avec enthousiasme.

PKCE ne protège pas contre un navigateur compromis, une session du serveur d’autorisation détournée, une redirection permissive ou un client qui accepte un jeton destiné à un autre destinataire. Il protège précisément l’échange d’un code intercepté. Cette précision compte : attribuer à PKCE des pouvoirs mystiques évite rarement l’incident suivant.

Scopes OAuth : réduire le blast radius avant l’incident

Section intitulée « Scopes OAuth : réduire le blast radius avant l’incident »

Un scope est une capacité nommée accordée au jeton, par exemple orders.read ou invoices.write. Il ne remplace pas un contrôle d’autorisation métier. Un scope orders.read ne dit pas quelles commandes sont visibles, ni si la personne peut voir les commandes d’une autre entité. Le serveur de ressources doit combiner le scope avec l’identité ou le contexte porté par le jeton, puis appliquer ses règles métier.

Accorder *, admin ou un catalogue entier de scopes par défaut est la faute la plus rentable pour un attaquant. Le client doit demander le minimum, le serveur d’autorisation doit consentir explicitement aux extensions de droits, et le serveur de ressources doit refuser tout scope absent. Un nouveau point d’accès ne doit jamais hériter silencieusement d’un jeton ancien parce que son nom ressemble vaguement à une permission existante.

Lier le jeton à une audience réduit aussi la réutilisation latérale. Une API de paie et une API de stockage doivent recevoir des jetons distincts, même si elles partagent le même serveur d’autorisation. Pour les architectures à plusieurs ressources, les indicateurs de ressource évitent qu’un jeton émis pour un premier service soit accepté par tous les voisins de palier.

La durée de vie d’un jeton d’accès doit rester courte, typiquement quelques minutes. Les jetons de rafraîchissement exigent une rotation : à chaque usage, émettre un nouveau jeton et invalider l’ancien. Détecter la réutilisation d’un ancien jeton signale une copie et doit entraîner la révocation de la famille concernée. Sans cette détection, la rotation est une porte tournante avec un panneau « sécurité ».

L’exemple suivant construit une demande d’autorisation pour un client public. Les valeurs doivent être générées par transaction, jamais figées dans une configuration.

Fenêtre de terminal
# Générer un secret temporaire PKCE, conservé localement jusqu'au retour.
VERIFIER="$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=')"
# Calculer le challenge S256 transmis au serveur d'autorisation.
CHALLENGE="$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
# La redirection doit être enregistrée exactement côté serveur d'autorisation.
# STATE doit être lié à la session locale et vérifié au retour.
curl -G 'https://auth.example.net/authorize' \
--data-urlencode 'response_type=code' \
--data-urlencode 'client_id=portal-public' \
--data-urlencode 'redirect_uri=https://portal.example.net/oauth/callback' \
--data-urlencode "code_challenge=$CHALLENGE" \
--data-urlencode 'code_challenge_method=S256' \
--data-urlencode 'scope=orders.read' \
--data-urlencode 'state=valeur-aleatoire-associee-a-la-session'

Au retour, vérifier state avant de traiter le code. L’échange vers le point de terminaison de jeton transmet ensuite le même VERIFIER, via un canal sécurisé par Transport Layer Security (TLS). Le code doit être consommé une seule fois et expirer rapidement. Journaliser l’identifiant du client, l’audience, les scopes, le résultat de l’échange et un identifiant de corrélation, jamais le code, le vérificateur ou le jeton brut.

Exploitation : rotation, révocation et supervision des jetons

Section intitulée « Exploitation : rotation, révocation et supervision des jetons »

Un déploiement OAuth exploitable exige un inventaire des clients, propriétaires, redirections, audiences et scopes. Chaque client confidentiel possède une méthode d’authentification propre : secret rotatif, certificat ou assertion signée. Un secret de client n’est pas un mot de passe partagé entre douze applications et un tableur oublié, même si cette tradition a une carrière étonnamment longue.

Les clés de signature du serveur d’autorisation doivent être publiées avec des identifiants de clé, permettre un chevauchement lors de la rotation et être conservées dans un système à accès contrôlé. Le serveur de ressources doit rafraîchir les clés publiques selon une politique bornée, accepter temporairement l’ancienne clé durant la rotation et alerter sur un identifiant inconnu. Pour les principes de garde de clés, voir les mécanismes cryptographiques.

La révocation n’annule pas instantanément un jeton d’accès déjà validé hors ligne. Une API qui vérifie localement un JWT continuera de l’accepter jusqu’à son expiration, sauf liste de révocation ou introspection en ligne. Le choix dépend de la criticité : durée courte et validation locale pour le débit, introspection ou mécanisme de révocation pour les opérations sensibles. Il faut annoncer ce délai comme une propriété d’architecture, pas le découvrir pendant une coupure de compte.

Superviser les échecs de validation state, les redirections refusées, les erreurs PKCE, les réutilisations de jetons de rafraîchissement, les consentements de scopes inhabituels et les échecs d’audience. Sauvegarder la configuration des clients, des politiques et des clés publiques, mais ne jamais restaurer aveuglément des secrets révoqués ou des jetons de rafraîchissement. La reprise doit préserver les identifiants de clés nécessaires à la vérification des jetons encore valides, puis forcer une rotation contrôlée.

Sans ces contrôles en service, la porte de la ruche reste entrouverte et l’analyse d’un incident devient impossible à borner. Sans inventaire des clients, des redirections et des audiences, aucun journal ne permet de dire quel client a obtenu quel jeton, ni de distinguer un appel légitime d’un appel rejoué : l’attribution se fait à l’estime. La remédiation perd alors sa granularité, puisque la seule action disponible reste la rotation globale des clés de signature, qui invalide toutes les sessions valides et coupe les intégrations saines en même temps que la fraude. Les consentements accordés deviennent illisibles, donc irrévocables individuellement, et la restauration d’une configuration ancienne réactive des clients ou des secrets déjà retirés. La supervision ne dispose plus d’aucun signal exploitable, faute d’identifiant de corrélation entre l’autorisation, l’échange de code et l’appel à la ressource.

OAuth sert à donner à une application un droit limité pour appeler un service au nom d’une personne, sans lui remettre son mot de passe. La personne se connecte auprès du service qui gère les connexions. Ce service renvoie ensuite un code court à l’application, sur une adresse de retour autorisée à l’avance. L’application échange ce code contre un jeton d’accès, qui indique quelles actions sont permises et pendant combien de temps.

Ce jeton ne prouve pas automatiquement l’identité de la personne pour tous les usages. Il donne seulement des droits précis à une application et à un service précis. Pour établir une identité de connexion, il faut utiliser les informations prévues à cet effet, puis vérifier leur origine et leur destinataire.

Une protection supplémentaire relie le code reçu à l’application qui a lancé la demande. L’application crée une valeur secrète temporaire et le service vérifie qu’elle possède toujours cette valeur lors de l’échange. Si un attaquant vole seulement le code, il ne peut pas terminer l’échange.

Si l’adresse de retour accepte n’importe quelle destination, le code peut être envoyé à un attaquant. Si les droits accordés sont trop larges, un jeton volé permet trop d’actions. Si les jetons durent trop longtemps, une révocation tardive laisse une fenêtre d’abus. Il faut donc limiter les droits, raccourcir les durées, renouveler les accès et surveiller les erreurs.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.