Ce qui se passe
Google accepte maintenant que le refresh token d'une application serveur soit lié à une clé cryptographique grâce à DPoP (Demonstrating Proof-of-Possession, norme RFC 9449, 2023). La nouveauté est le support de Google, pas le mécanisme : Okta et FusionAuth le proposent depuis un moment. L'annonce officielle date du 15 octobre 2026, mais l'auteur du test a retrouvé la trace du sujet dans les bonnes pratiques de Google dès le 20 mai, et la documentation du serveur web n'a été mise à jour que le 16 octobre.
Le principe : le serveur génère une paire de clés (EC P-256), signe un JWT, la preuve, à chaque appel du point d'accès de jetons, et Google émet un refresh token lié à la clé publique. Une base de données ou des journaux qui fuient ne livrent plus qu'un refresh token inutilisable sans la clé.
Ce que le test a trouvé
Le développeur a confronté la documentation officielle, le RFC 9449 et ses propres
mesures à de vrais échanges de jetons, sur un client confidentiel (Node 24.22.0,
jose 6.2.12, un compte et un client dédiés). Son verdict : l'implémentation
fonctionne, la documentation non.
Choix de conception documentés mais différents du RFC.
L'access_token reste un Bearer (seul le refresh token est lié) : Google invoque
le coût de la vérification de signature à chaque requête d'une API à très fort trafic.
Mesuré : 26,2 µs pour signer en ES256, 50,5 µs pour vérifier, soit environ 20 000
vérifications par seconde et par cœur. Lors de l'échange du code d'autorisation, le
jti est un hachage déterministe, BASE64URL(SHA-256(code)), alors que le RFC exige
une valeur aléatoire ; au refresh, il est aléatoire (192 bits).
Documentation contredite par les essais.
Un exemple officiel de la page d'adoption porte une signature de 70 octets au format DER, alors qu'un JWS attend le format brut
R || Sde 64 octets :TEXT crypto.subtle.sign -> 64 octets, format brut R||S (celui de JWS) node:crypto createSign -> 71 octets, DER ASN.1 (premier octet 0x30)Tester
length === 70ne reconnaît pas le DER : sa longueur varie de 69 à 72.Deux pages se contredisent sur le nonce de l'échange de code : le test donne raison à la plus ancienne. Le premier refresh renvoie
400 use_dpop_nonce; la boucle de réessai est obligatoire, deux allers-retours au premier refresh.Les erreurs : un
htmou unhtufaux donneinvalid_request(et noninvalid_dpop_proof), une signature invalide ou uniathors fenêtre donneinvalid_dpop_proof.« Un nonce n'est utilisé qu'une fois » : six requêtes avec le même nonce, dont une 30 secondes plus tard, ont toutes réussi. Un même
jtirejoué aussi.
Non documenté. La preuve est acceptée de 3 600 s dans le passé mais seulement
10 s dans le futur : une horloge de serveur en avance de 11 secondes fait rejeter
chaque preuve. La même preuve rejouée avec un refresh token valide passe à 59 minutes
et échoue à 61. L'octroi est vérifié avant la preuve : un refresh token expiré donne
invalid_grant quelle que soit l'erreur dans la preuve, ce qui masque les bugs.
Google n'annonce pas DPoP dans .well-known/openid-configuration.
Les limites, dont celles du test
Le conseil de Google de créer la clé en extractable: false est incompatible avec
Node : impossible de l'exporter, donc de la conserver ; au redémarrage, tous les
refresh tokens liés à cette clé deviennent inutilisables, et seule une nouvelle
autorisation de chaque utilisateur les remplace. Il faut une clé persistante, par
exemple exportée puis chiffrée ou gardée dans un KMS : la sauvegarde de la clé
devient la sauvegarde des refresh tokens. Migrer des utilisateurs existants exige
aussi de révoquer l'ancien octroi ou de repasser par prompt=consent.
Le rejeu n'est pas une faille démontrée. L'auteur a signalé l'écart nonce/jti à
Google, qui l'a classé Won't Fix (Infeasible), et il le reconnaît : un attaquant
capable de lire la preuve lirait aussi le refresh_token et le client_secret du
même corps de requête, et une preuve seule est rejetée en invalid_grant. Ce sont
des mesures d'une personne sur des comptes de test ; la documentation de Google reste
la référence.
Que faire
- Stockez-vous des refresh tokens Google côté serveur ? DPoP réduit l'effet d'une fuite de base de données. Prévoir d'abord la persistance chiffrée de la clé.
- Écrire la boucle de réessai sur
use_dpop_nonce, et signer en format brut. - Tester le refresh sans DPoP d'abord, puis l'ajouter, pour isoler les erreurs.
- Vérifier la synchronisation NTP des serveurs.
Source : DEV Community, 20 octobre 2026, mesures du 19 octobre. Source en anglais. Annonce de Google : Announcing DPoP Support for Google OAuth.