Blog d'Obi Madu
Retour à tous les articles
System DesignInfrastructureBackend

OAuth vs OIDC : La différence enfin expliquée

Une explication pratique d'OAuth, OIDC, des jetons d'accès et des jetons d'ID sans la confusion habituelle liée à l'authentification.

OAuth vs OIDC : La différence enfin expliquée

OAuth et OpenID Connect sont deux technologies profondément fondamentales que les développeurs modernes utilisent constamment, et pourtant ils les décrivent fréquemment de manière imprécise. Les gens disent avec désinvolture des choses comme « connexion OAuth » ou « se connecter en toute sécurité avec OAuth ». Parfois, cette formulation détendue n'est qu'un jargon inoffensif de l'industrie, mais d'autres fois, elle masque de manière critique une distinction architecturale incroyablement importante. OAuth 2.0 est fondamentalement un framework entièrement construit autour de l'autorisation. OpenID Connect, presque universellement abrégé en OIDC, est une couche d'identité strictement conçue pour l'authentification.

Cette différence sémantique semble purement académique jusqu'à ce que vous construisiez réellement une véritable application prête pour la production. Si vous fonctionnez avec un mauvais modèle mental, vous pouvez facilement finir par traiter un jeton d'accès (access token) standard comme une preuve définitive de l'identité de l'utilisateur, en sautant dangereusement des étapes critiques de vérification des jetons, en stockant des jetons hautement sensibles de manière non sécurisée sur des appareils mobiles, ou en supposant bêtement que chaque fournisseur d'identité se comporte exactement comme Google.

La version absolument la plus simple de cette distinction est celle-ci : OAuth répond fermement à la question cruciale : « Que cette application spécifique est-elle exactement autorisée à faire ? » Pendant ce temps, OIDC répond clairement à une question tout à fait différente : « Qui est précisément cet utilisateur spécifique ? » OIDC est littéralement construit directement par-dessus le framework établi OAuth 2.0, ajoutant de manière transparente une couche d'identité robuste et hautement standardisée au flux d'autorisation existant d'OAuth.

L'analogie du bracelet et de la carte d'identité

Imaginez que vous essayez d'entrer dans une discothèque exclusive.

OAuth, c'est essentiellement comme recevoir un bracelet de couleur vive à la porte. Il indique clairement au videur et au personnel du lieu exactement à quelles zones vous êtes formellement autorisé à accéder. Votre bracelet signifie peut-être que vous pouvez entrer dans la zone VIP délimitée, ou bien il indique que vous êtes autorisé à commander directement au bar premium. Dans tous les sens du terme, le bracelet représente uniquement une permission accordée. Mais de manière cruciale, le bracelet ne prouve absolument pas qui vous êtes réellement. Il n'affiche pas votre nom légal, il n'a pas votre photo, il ne confirme pas votre âge et il ne contient aucun numéro d'identité officiel.

OIDC, c'est précisément comme recevoir exactement ce même bracelet tout en étant simultanément obligé de porter une carte d'identité gouvernementale hautement fiable. Vous conservez toujours toutes vos permissions accordées via le bracelet, mais vous possédez désormais également un document d'identité largement reconnu et standardisé qui indique définitivement qui vous êtes réellement.

Ce scénario est exactement ce qui se passe techniquement dans le monde des logiciels :

OAuth 2.0 gives you:
  Access token
  Used to call APIs

OIDC gives you:
  Access token
  Used to call APIs

  ID token
  Used to identify the user

Le jeton d'accès et le jeton d'ID sont des frères fonctionnels. Un jeton n'est absolument pas un substitut ou un remplacement pour l'autre.

Ce que fait réellement OAuth 2.0

Dans son cœur absolu, OAuth 2.0 permet simplement à un utilisateur d'accorder en toute sécurité à une application tierce la permission explicite d'accéder en son nom à une ressource hautement protégée. Un exemple concret très courant consiste à connecter une nouvelle application à votre compte GitHub existant. L'application redirige l'utilisateur de manière transparente directement vers GitHub et demande explicitement un accès ciblé au dépôt.

https://github.com/login/oauth/authorize?
  client_id=abc123&
  scope=repo&
  redirect_uri=https://myapp.com/callback

GitHub présente ensuite à l'utilisateur un écran de consentement clair. Si l'utilisateur approuve la demande de manière affirmative, GitHub le redirige de manière sécurisée vers l'application d'origine, en transportant un code d'autorisation temporaire.

https://myapp.com/callback?code=xyz789

En coulisses, l'application échange ensuite en toute sécurité ce code temporaire contre un jeton d'accès beaucoup plus durable.

POST https://github.com/login/oauth/access_token
Content-Type: application/x-www-form-urlencoded

client_id=abc123&
client_secret=secret456&
code=xyz789&
redirect_uri=https://myapp.com/callback

La réponse du fournisseur contient de manière importante le jeton d'accès nouvellement créé.

{
  "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a",
  "token_type": "bearer",
  "scope": "repo"
}

Ce jeton spécifique peut maintenant être utilisé à plusieurs reprises pour appeler des API GitHub hautement protégées au nom de l'utilisateur.

GET https://api.github.com/user/repos
Authorization: Bearer gho_16C7e42F292c6912E7710c838347Ae178B4a

Remarquez attentivement ce qu'OAuth ne nous a fondamentalement pas donné tout au long de ce processus. Il ne nous a absolument donné aucun type de jeton d'identité standard. Il nous a strictement donné une permission très délimitée pour appeler une API. Avec GitHub spécifiquement, si vous voulez réellement déterminer l'identité de l'utilisateur, vous êtes obligé d'utiliser ce jeton d'accès pour appeler manuellement une API d'identité explicite, telle que GET /user. Cette étape supplémentaire existe précisément parce que le flux de connexion le plus courant de GitHub utilise un OAuth 2.0 pur, plutôt qu'OIDC.

Ce qu'OIDC ajoute

OIDC commence intelligemment par exactement le même flux OAuth de base, mais ajoute explicitement une portée (scope) d'identité hautement requise et standardisée appelée openid. Par exemple, une demande typique de connexion Google inclut presque toujours un groupe de portées qui ressemble exactement à ceci :

scope=openid profile email

Cette simple portée openid agit comme le signal définitif au fournisseur qu'il s'agit formellement d'un flux OpenID Connect. L'application qui fait la demande déclare explicitement qu'elle ne demande pas seulement un accès standard à l'API ; elle demande activement au fournisseur sous-jacent d'authentifier rigoureusement l'utilisateur, puis de renvoyer ses informations d'identité dans un format hautement standardisé et mondialement reconnu.

Une fois l'échange de code standard terminé, un fournisseur compatible OIDC renvoie de manière prévisible à la fois un jeton d'accès et un jeton d'ID simultanément.

{
  "access_token": "ya29.a0AfH6SMBxC...",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjE2...",
  "refresh_token": "1//04dGKC8...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Le jeton d'accès reste fermement désigné exclusivement pour appeler des API. Le jeton d'ID nouvellement introduit fonctionne comme le document d'identité hautement fiable. De manière cruciale, le jeton d'ID est presque toujours formaté comme un JWT (JSON Web Token), ce qui dicte strictement qu'il possède trois parties distinctes : un en-tête (header), une charge utile (payload) et une signature cryptographique.

header.payload.signature

La charge utile centrale contient en grande partie des revendications (claims) détaillées concernant l'utilisateur spécifique et les circonstances exactes de l'événement d'authentification lui-même.

{
  "sub": "109651783456723954281",
  "name": "John Doe",
  "email": "john.doe@gmail.com",
  "email_verified": true,
  "picture": "https://lh3.googleusercontent.com/a-/abc123",
  "iss": "https://accounts.google.com",
  "aud": "1234567890-abcd1234.apps.googleusercontent.com",
  "iat": 1712345678,
  "exp": 1712349278
}

La revendication sub sert de manière cohérente d'ID utilisateur unique et hautement stable du côté du fournisseur. La revendication iss vous informe de manière fiable de l'identité exacte de celui qui a émis le jeton en toute sécurité. La revendication aud indique de manière définitive pour quelle application cliente spécifique le jeton a été intentionnellement émis. La revendication exp définit clairement et mathématiquement quand le jeton expire. Cependant, ces revendications intégrées sont complètement inutiles et potentiellement dangereuses à moins que vous ne vérifiiez activement et correctement l'intégrité cryptographique du jeton au préalable.

Ne vous contentez jamais de décoder un jeton d'ID

Une erreur de débutant incroyablement courante et incroyablement dangereuse consiste à diviser paresseusement le JWT, à décoder la charge utile en base64 et à faire aveuglément confiance au texte qu'il se trouve contenir. Ce processus imprudent n'est absolument pas une vérification ; c'est simplement de l'analyse de texte.

Un JWT n'est véritablement utile que précisément parce qu'il est signé cryptographiquement par l'émetteur. Votre backend sécurisé doit rigoureusement vérifier cette signature intégrée par rapport aux clés hébergées publiquement par le fournisseur d'identité. De plus, il doit valider fortement l'émetteur, vérifier strictement l'audience, imposer l'heure d'expiration et valider agressivement toute autre revendication spécifique dont votre application dépend de manière critique.

La liste de contrôle de vérification backend standard est remarquablement simple dans son concept de base :

VérificationPourquoi c'est important
SignatureProuve que le jeton provient réellement du fournisseur de confiance
Émetteur (Issuer)Empêche d'accepter des jetons usurpés provenant entièrement du mauvais fournisseur
AudienceEmpêche d'accepter accidentellement des jetons valides destinés à une autre application
ExpirationEmpêche strictement la réutilisation malveillante d'anciens jetons expirés
Vérification de l'e-mailEmpêche efficacement de faire aveuglément confiance à des adresses e-mail non vérifiées et potentiellement fausses

Une version hautement simplifiée d'une fonction de vérification backend sécurisée ressemble généralement à ceci :

def verify_token(token):
    public_key = fetch_provider_public_key(token)

    payload = jwt.decode(
        token,
        public_key,
        algorithms=["RS256"],
        audience=YOUR_CLIENT_ID,
        issuer="https://accounts.google.com",
    )

    if not payload.get("email_verified"):
        raise ValueError("Email is not verified")

    return payload

Dans un véritable environnement de production, vous avez invariablement aussi besoin de mécanismes robustes de mise en cache JWKS, d'une logique sophistiquée de gestion de la rotation des clés et de règles de validation profondément spécifiques au fournisseur. L'idée primordiale la plus importante est que la vérification robuste de l'identité a fondamentalement sa place en toute sécurité sur le backend, et absolument pas simplement dans des charges utiles de jetons décodées côté frontend et facilement manipulables.

Les différences entre les fournisseurs comptent

Il est vital de comprendre que chaque bouton « Se connecter avec X » n'utilise pas réellement OIDC sous le capot. Des fournisseurs massifs comme Google, Apple, Microsoft, Auth0, Keycloak et Zitadel, ainsi que de nombreuses plateformes d'identité modernes, prennent entièrement en charge la norme OIDC. Dans ces flux hautement modernes, vous pouvez vous attendre de manière fiable à recevoir un jeton d'ID standard et vérifiable.

Cependant, le flux d'application OAuth le plus couramment utilisé par GitHub est distinctement différent. Il ne fournit généralement qu'un jeton d'accès. Pour acquérir réellement les informations de profil de l'utilisateur, vous êtes obligé d'utiliser ce jeton pour appeler manuellement l'API GitHub.

const userResponse = await fetch("https://api.github.com/user", {
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

const user = await userResponse.json();

Cette approche n'est pas nécessairement intrinsèquement pire, mais elle est indéniablement différente. Un OAuth pur peut absolument toujours être utilisé avec succès dans les flux de connexion modernes, mais le mécanisme de récupération d'identité lui-même devient entièrement spécifique au fournisseur, à moins que le fournisseur choisi ne prenne explicitement en charge la norme OIDC. Cette distinction nuancée affecte également fortement les stratégies de liaison de comptes. Avec le véritable OIDC, la revendication sub agit systématiquement comme la clé d'identité hautement stable pour ce fournisseur spécifique. Avec les fournisseurs uniquement OAuth, cependant, vous devez récupérer manuellement et stocker avec précaution l'ID utilisateur unique du fournisseur dérivé directement de sa réponse d'API personnalisée.

Auth gérée vs OIDC auto-hébergé

Dans le monde réel du développement d'applications, vous êtes presque toujours confronté à un choix entre utiliser un fournisseur d'authentification entièrement géré et implémenter minutieusement une intégration OIDC robuste entièrement par vous-même.

Les services gérés comme Clerk, Auth0 et Firebase Auth existent spécifiquement pour abstraer et masquer la grande majorité des détails protocolaires pénibles. Ils gèrent sans effort la configuration dense des fournisseurs, les échanges de jetons complexes, le rafraîchissement agressif des jetons, la gestion sécurisée des sessions, les enregistrements d'utilisateurs persistants et les SDK profondément intégrés. Cette approche « clé en main » est incroyablement attrayante lorsque la vitesse de développement compte réellement.

À l'inverse, les fournisseurs d'identité auto-hébergés comme Zitadel ou Keycloak offrent un contrôle beaucoup plus granulaire. Ils constituent fréquemment un choix largement supérieur pour les exigences strictes de souveraineté des données, la conformité réglementaire intense, la personnalisation approfondie des flux de travail ou les économies de coûts massives à l'échelle de l'entreprise. Cependant, le compromis est que vous êtes lourdement responsable d'une bien plus grande partie de l'effort d'intégration.

Avec un fournisseur géré, votre backend peut simplement s'appuyer sur la vérification d'un jeton de session léger généré explicitement par le SDK personnalisé de ce fournisseur.

async def require_auth(request):
    request_state = clerk.authenticate_request(
        request={"headers": dict(request.headers), "url": str(request.url)}
    )

    if not request_state.is_signed_in:
        raise HTTPException(status_code=401)

    return request_state.payload

Avec un fournisseur OIDC auto-hébergé, vous êtes généralement entièrement responsable de la configuration manuelle du point de terminaison d'autorisation, du point de terminaison de jeton, de l'ID client, de l'URI de redirection, des portées demandées et de l'intense logique de vérification JWT du backend entièrement à partir de zéro. Aucune de ces approches spécifiques n'est universellement correcte ou incorrecte. L'authentification gérée réduit considérablement la charge opérationnelle, tandis que l'auto-hébergement augmente considérablement le contrôle à long terme.

Les applications mobiles ont besoin de PKCE

Il est absolument crucial de réaliser que les applications mobiles ne sont fondamentalement pas exactement les mêmes que les applications web traditionnelles rendues par le serveur. Une application web traditionnelle peut stocker en toute sécurité et de manière permanente un secret client hautement sensible entièrement sur le serveur backend. Une application mobile native ne le peut absolument pas. Littéralement tout ce qui est compilé et expédié dans un fichier APK ou IPA public peut être extrait de manière triviale par un utilisateur modérément déterminé ou un script automatisé. Cette dure réalité signifie que les applications mobiles natives doivent sans équivoque être traitées comme des « clients publics » hautement vulnérables.

Cette vulnérabilité flagrante est exactement la raison pour laquelle tous les flux OAuth et OIDC mobiles devraient utiliser rigoureusement PKCE, qui signifie Proof Key for Code Exchange. PKCE ajoute ingénieusement un secret temporaire et hautement dynamique par connexion au flux. L'application mobile génère localement un code_verifier aléatoire, le hache de manière sécurisée en un code_challenge envoyé lors de la demande d'autorisation initiale, et prouve plus tard de manière concluante qu'elle connaît le vérificateur d'origine lorsqu'elle échange finalement le code contre des jetons réels.

1. App creates a random code_verifier
2. App hashes it into a code_challenge
3. Authorization request includes the code_challenge
4. Provider returns an authorization code
5. Token request includes the original code_verifier
6. Provider checks that it matches the challenge

Dans le populaire écosystème Expo, cette danse complexe est presque toujours gérée proprement et directement via la bibliothèque expo-auth-session.

const [request, response, promptAsync] = AuthSession.useAuthRequest(
  {
    clientId: CLIENT_ID,
    scopes: ["openid", "profile", "email"],
    redirectUri,
    usePKCE: true,
  },
  discovery
);

De plus, les applications mobiles ont désespérément besoin d'un stockage local de jetons incroyablement sécurisé. Vous ne devez absolument jamais vider de jetons hautement sensibles dans un stockage asynchrone non chiffré et facilement lisible. Dans un environnement Expo, vous devez toujours utiliser rigoureusement expo-secure-store pour verrouiller tout matériel de jeton sensible.

import * as SecureStore from "expo-secure-store";

await SecureStore.setItemAsync("id_token", idToken);
const savedToken = await SecureStore.getItemAsync("id_token");

Les trois couches de jetons

Une source massive et persistante d'immense confusion est le simple fait que les applications modernes jonglent presque toujours avec bien plus d'un seul type de jeton simultanément. Le fournisseur d'identité externe peut facilement émettre à la fois un jeton d'ID et un jeton d'accès distinct. En réponse, votre backend interne peut alors créer dynamiquement son tout propre jeton de session propriétaire. Pour compliquer les choses, le fournisseur pourrait également émettre simultanément un jeton de rafraîchissement de longue durée.

Ces jetons très variés servent des objectifs complètement différents et hautement spécialisés au sein de l'architecture.

JetonÉmetteurObjectif
Jeton d'IDFournisseur d'identitéProuve définitivement qui est exactement l'utilisateur
Jeton d'accèsFournisseur d'identité ou d'APIAutorise strictement les appels d'API en aval
Jeton de session de l'AppVotre app ou plateforme d'authAuthentifie les requêtes continues directement vers votre backend
Jeton de rafraîchissementFournisseurAcquiert en toute sécurité de nouveaux jetons de courte durée lorsque les anciens expirent

Un flux architectural très commun et très robuste ressemble généralement à peu près à ceci :

Google ID token -> Your backend verifies it -> Your backend creates an app session

Suite à cet échange initial, votre application cliente peut simplement utiliser votre propre jeton de session fortement personnalisé pour appeler efficacement votre API interne. Elle n'a absolument pas besoin de transmettre en continu le jeton d'ID Google massif à chaque requête réseau, à moins que ce ne soit spécifiquement ainsi que votre architecture particulière a été intentionnellement conçue.

Malentendus courants

L'erreur absolue la plus courante et la plus répandue dans l'industrie est de déclarer avec désinvolture qu'OAuth signifie littéralement « connexion ». OAuth peut certainement être largement utilisé pour participer de manière sécurisée à un flux de connexion plus large, mais OAuth lui-même concerne strictement et entièrement l'autorisation déléguée. OIDC est la véritable couche d'identité, fortement standardisée, construite spécifiquement pour connecter les gens.

Une autre erreur massive et fréquemment rencontrée est de tenter d'utiliser le jeton d'ID pour autoriser directement les appels d'API. Le jeton d'ID existe exclusivement pour que le client frontend ou le serveur backend comprenne définitivement l'identité de l'utilisateur. Le jeton d'accès existe exclusivement pour une autorisation d'API robuste. Brouiller ces deux lignes conduit constamment à de graves failles architecturales.

Le comportement de déconnexion représente un autre domaine de confusion incroyablement subtil. Déconnecter un utilisateur de votre application spécifique efface généralement simplement votre session d'application locale et supprime furieusement les jetons locaux. Cela ne force absolument pas nécessairement l'utilisateur à se déconnecter de Google, d'Apple ou de chaque autre application connectée se trouvant sur son appareil physique. Cette séparation ferme de l'état de session est tout à fait normale et attendue.

Enfin, les portées de permission demandées doivent toujours être maintenues aussi minimales qu'il est humainement possible. Si votre application n'a vraiment besoin de connaître que l'identité de l'utilisateur, vous ne devriez jamais demander que les portées openid profile email. Vous ne devriez absolument jamais demander avec désinvolture un accès étendu à son calendrier, son disque privé, ses photos personnelles ou d'autres autorisations étendues, à moins que votre application n'en ait réellement fondamentalement besoin pour fonctionner.

Le résumé pratique

OAuth 2.0 est fondamentalement le framework de permissions. Il permet définitivement à une application donnée de faire quelque chose d'incroyablement spécifique au nom d'un utilisateur. OIDC est la couche d'identité standardisée reposant directement par-dessus. Il permet de manière sécurisée à une application de savoir de manière fiable exactement qui est réellement l'utilisateur d'une manière universellement standardisée.

Utilisez le jeton d'accès exclusivement pour appeler des API. Utilisez le jeton d'ID exclusivement pour vérifier l'identité. Vérifiez toujours rigoureusement les jetons directement sur le backend. Imposez toujours agressivement PKCE pour les applications mobiles natives. Stockez toujours les jetons sensibles dans un stockage hautement sécurisé et chiffré. Traitez toujours les différences entre les fournisseurs d'identité comme des contraintes architecturales très réelles, et non pas simplement comme des variations cosmétiques.

Si vous ne retenez absolument rien d'autre de cet article, forcez-vous à vous souvenir de cette seule règle d'or : OAuth donne simplement à votre application une permission, mais OIDC donne définitivement à votre application une identité.

Références