Le 2 août 2026, OpenAI a mis « Sign in with ChatGPT » en bêta. Pas de nouveau protocole. C'est OAuth 2.0 + OpenID Connect, flux Authorization Code, exactement le socle de « Sign in with Google ». Si tu as déjà branché un provider OIDC, tu connais 90 % du travail, et les 10 % restants sont précisément là où les intégrations se cassent — vérification du token, nonce, lock-in. C'est de ça qu'on parle.
Six partenaires étaient présents au lancement : Airtable, GitLab, HubSpot, Notion, Supabase, Vercel. OpenAI pousse les devs à tester avec des crédits API (+5 $ en Plus, +50 $ en Pro, d'après une source unique à reconfirmer). L'incitation est réelle. Mais elle ne change rien au vrai sujet : ce que ton backend fait du token une fois qu'il l'a reçu.
Le flux, en six temps
OpenAI devient un fournisseur d'identité. Ton app ne voit jamais le mot de passe de l'utilisateur ; elle reçoit un jeton signé qui atteste « cette personne est bien untel ». Le flux Authorization Code, avec PKCE, se déroule ainsi :
- Ton serveur redirige l'utilisateur vers l'endpoint d'autorisation d'OpenAI, avec
client_id,redirect_uri,scope=openid profile email, unstateet uncode_challenge(PKCE). - L'utilisateur s'authentifie chez OpenAI et consent au partage.
- OpenAI le renvoie sur ta
redirect_uriavec uncodecourt et lestate. - Ton backend échange ce
codecontre unaccess_tokenet unid_token(un JWT). - Tu vérifies la signature de l'
id_token, puisiss,aud,expet lenonce. - Tu lis les claims :
sub,name,email,picture. Fin.
Le point clé est à l'étape 4. Le code ne voyage jamais dans le navigateur au-delà d'un aller simple, et l'échange se fait de serveur à serveur, ton client_secret à l'appui. C'est ce qui rend le flux Authorization Code plus sûr que l'ancien flux implicite, désormais déconseillé.
Le code qui marche (Node + openid-client)
Ne réécris pas la vérification cryptographique à la main. La lib openid-client gère la découverte, PKCE, le state, le nonce et la validation JWKS. Voici la configuration, à partir du document de découverte OIDC :
import { Issuer, generators } from 'openid-client';
// L'issuer exact est fourni dans la console OpenAI quand tu enregistres ton app.
const openai = await Issuer.discover('https://auth.openai.com');
const client = new openai.Client({
client_id: process.env.OPENAI_CLIENT_ID,
client_secret: process.env.OPENAI_CLIENT_SECRET,
redirect_uris: ['https://ton-app.com/auth/chatgpt/callback'],
response_types: ['code'],
});
La redirection génère et stocke les secrets éphémères — code_verifier, state, nonce — côté session :
app.get('/auth/chatgpt', (req, res) => {
const code_verifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(code_verifier);
const state = generators.state();
const nonce = generators.nonce();
req.session.pkce = { code_verifier, state, nonce };
const url = client.authorizationUrl({
scope: 'openid profile email',
code_challenge,
code_challenge_method: 'S256',
state,
nonce,
});
res.redirect(url);
});
Le callback fait le vrai travail. Un seul appel échange le code, vérifie la signature contre le JWKS d'OpenAI et recale state comme nonce :
app.get('/auth/chatgpt/callback', async (req, res) => {
const { code_verifier, state, nonce } = req.session.pkce;
const params = client.callbackParams(req);
const tokenSet = await client.callback(
'https://ton-app.com/auth/chatgpt/callback',
params,
{ code_verifier, state, nonce },
);
const claims = tokenSet.claims();
// { sub, name, email, picture, iss, aud, exp, iat, ... }
res.json({ id: claims.sub, name: claims.name, email: claims.email });
});
Les trois pièges qui cassent tout
Le flux paraît simple, et c'est bien le problème : on croit avoir fini alors qu'on vient d'ouvrir une faille.
-
Décoder n'est pas vérifier. Un
jwt.decode(id_token)te rend les claims sans contrôler la signature. N'importe qui peut forger un JWT avecemail: patron@boite.com, et sans validation JWKS, tu le gobes. Il faut vérifier la signature contre les clés publiques d'OpenAI, à chaque connexion. -
Le
nonceoublié. Sans lui, unid_tokenintercepté peut être rejoué. Tu le génères à l'aller, tu le stockes en session, tu vérifies qu'il revient identique — c'est exactement ce queopenid-clientfait si tu le lui passes, d'où l'intérêt de ne pas « simplifier » l'appelcallback. -
audetissnon contrôlés. Un token parfaitement signé mais émis pour une autre app, ou par un autre émetteur, doit être rejeté. C'est le genre de trou qu'un pentest trouve en dix minutes.
Le sub est ton identifiant stable, pas l'e-mail. Un utilisateur peut changer d'adresse ; le sub d'OpenAI, non. Clé ta table utilisateurs dessus, sinon tu fusionneras deux comptes le jour d'un changement d'e-mail.
Ce que tu reçois, et ce que tu paies
Côté données, ton app ne récupère que trois champs : nom, e-mail, photo de profil. C'est peu. Ça t'évite surtout de stocker un mot de passe. Mais l'échange a un prix qu'aucun console.log ne t'affichera : à chaque connexion, OpenAI voit quelle app, quel appareil, quand et où — et aucune politique de rétention publique n'existe pour ces logs.
Ajoute le reste de la facture avant d'appuyer sur merge : c'est une bêta, l'API peut bouger sans préavis, et tu déportes ton login layer chez un tiers. Ce lock-in est le vrai arbitrage, pas la difficulté technique — elle, tu l'as réglée en trois handlers. La question « à qui appartient l'accès à mes utilisateurs ? » rejoint celle de la propriété du code : on ne la voit pas tant que tout va bien.
Mon conseil de terrain : implémente-le en secondaire, jamais comme unique porte d'entrée. Garde un e-mail/mot de passe ou un second provider, et fais du sub OpenAI un lien parmi d'autres vers ton compte. Le jour où la bêta casse, tes utilisateurs se connectent quand même.
Neova est un studio de développement. Nous construisons des applications sur mesure, et l'auth en fait partie — c'est ce qui nous a poussés à disséquer ce flux le jour du lancement. Le décryptage des deux sens de « se connecter avec ChatGPT » est sur le blog.
Top comments (0)