Aller au contenu
medium

OpenID Connect (OIDC) : authentification moderne et SSO

24 min de lecture

OpenID Connect (OIDC) ajoute l'identité à OAuth 2.0. Là où OAuth répond "cette app peut accéder à l'API", OIDC répond "cet utilisateur est alice@exemple.com". C'est le standard derrière tous les boutons "Se connecter avec..." et la base du SSO (Single Sign-On) moderne. Un seul login, accès à toutes vos applications.

Ce guide vous explique OpenID Connect (OIDC), le standard pour l'authentification moderne. À la fin, vous saurez :

  • Comprendre ce qu'OIDC ajoute à OAuth 2.0 (spoiler : l'identité)
  • Décoder un ID Token et comprendre ses claims
  • Valider correctement un JWT (signature, issuer, audience, expiration)
  • Configurer une application avec Discovery et JWKS

Prérequis : avoir lu OAuth 2.x.

OpenID Connect (OIDC) est un protocole d'authentification qui permet à une application de vérifier l'identité d'un utilisateur et de récupérer ses informations de profil, nom, adresse e-mail, auprès d'un service tiers de confiance. Publié en 2014 par l'OpenID Foundation, il est devenu le standard de l'authentification web moderne : chaque bouton « Se connecter avec Google » ou « Se connecter avec Microsoft » repose sur lui.

Concrètement, OIDC répond à une seule question : « qui est l'utilisateur connecté ? ». Votre application délègue la vérification du mot de passe, et du second facteur, à un fournisseur d'identité, appelé OpenID Provider, qui lui renvoie une preuve signée et infalsifiable : l'ID Token. L'application n'a jamais à stocker ni même à voir le mot de passe de l'utilisateur.

C'est cette délégation qui rend le SSO (Single Sign-On) possible : un seul login sur le fournisseur d'identité ouvre l'accès à toutes les applications qui lui font confiance. OIDC ne réinvente rien, il s'appuie sur le protocole OAuth 2.0, auquel il ajoute une couche d'identité. Il joue un rôle comparable à SAML, le standard de fédération plus ancien, mais avec un jeton plus léger, du JSON plutôt que du XML, mieux adapté aux applications web et mobiles actuelles.

OAuth 2.0 répond à une question d'autorisation : « cette application a-t-elle le droit d'accéder à ces données ? ». OIDC répond à une question d'authentification : « qui est l'utilisateur connecté ? ». La nuance paraît théorique, elle a des conséquences très concrètes.

Beaucoup d'applications ont longtemps bricolé une authentification par-dessus OAuth 2.0 seul, en appelant une API de profil avec l'access token et en supposant que la réponse identifiait l'utilisateur. Ce raccourci est vulnérable : un access token volé sur un autre site permet alors d'usurper le compte, car rien ne lie le jeton à votre application. OIDC ferme cette faille en normalisant une preuve d'authentification dédiée, l'ID Token, dont l'audience désigne explicitement le destinataire.

La spécification OIDC renomme les acteurs d'OAuth 2.0. C'est une source de confusion permanente quand on lit deux documentations en parallèle : l'Authorization Server d'OAuth et l'OpenID Provider d'OIDC désignent la même machine. Retenez surtout les deux dernières lignes du tableau : ce sont les seuls éléments réellement nouveaux, tout le reste est un changement de nom.

Terme OAuth 2.0Équivalent OIDC
Authorization ServerOpenID Provider (OP)
ClientRelying Party (RP)
(aucun)ID Token (nouveau)
(aucun)UserInfo Endpoint (nouveau)

L'ID Token est un JWT (JSON Web Token) qui contient l'identité de l'utilisateur authentifié.

Un JWT se compose de 3 parties séparées par des points :

header.payload.signature

Chaque partie est encodée en base64url. Point important pour la sécurité : base64url n'est pas du chiffrement. N'importe qui peut lire le contenu d'un ID Token intercepté avec un simple décodage. Ce qui protège le jeton, ce n'est pas l'encodage, c'est la signature de la troisième partie, qui rend toute modification détectable. Ne placez donc jamais de donnée confidentielle dans un ID Token.

Voici à quoi ressemble un ID Token une fois ses trois parties décodées. Le header annonce l'algorithme de signature et surtout le kid, l'identifiant de la clé publique à récupérer pour vérifier ce jeton précis. Le payload porte les claims, c'est-à-dire les affirmations du fournisseur d'identité sur l'utilisateur. Rien de tout cela n'a de valeur tant que la signature n'a pas été vérifiée.

Header :

{
"alg": "RS256",
"typ": "JWT",
"kid": "key-2024-02"
}

Payload (les claims) :

{
"iss": "https://auth.example.com",
"sub": "user-123",
"aud": "my-app-client-id",
"exp": 1700000000,
"iat": 1699996400,
"nonce": "abc123xyz",
"email": "alice@example.com",
"name": "Alice Martin",
"email_verified": true
}

Signature : permet de vérifier que le token n'a pas été modifié.

Ces claims forment le socle que tout OpenID Provider conforme doit émettre. Les cinq premiers sont ceux que votre code doit contrôler systématiquement : ils ne servent pas à afficher un profil, ils servent à décider si le jeton vous concerne et s'il est encore valable. Le couple iss et sub mérite une attention particulière : c'est leur combinaison qui identifie un utilisateur de façon unique, jamais le sub seul, car deux fournisseurs différents peuvent émettre le même identifiant.

ClaimDescriptionObligatoire
issIssuer, URL de l'OP qui a émis le token
subSubject, identifiant unique de l'utilisateur
audAudience, client_id de l'application destinataire
expExpiration, timestamp UNIX
iatIssued At, timestamp de création
nonceAnti-replay, valeur envoyée à l'autorisation⚠️ Recommandé
auth_timeMoment de l'authentificationOptionnel
acrAuthentication Context Class ReferenceOptionnel

Ces claims décrivent l'utilisateur et dépendent des scopes demandés au moment de l'autorisation : sans scope=profile, ne comptez pas voir arriver name ou picture. Deux réflexes à garder. D'abord, aucun de ces champs n'est garanti : un fournisseur peut très bien ne pas renvoyer email si l'utilisateur n'en a pas déclaré. Ensuite, ne vous servez jamais de l'adresse e-mail comme identifiant de compte dans votre base, elle peut changer ; utilisez sub, qui reste stable pour la durée de vie du compte.

ClaimDescription
nameNom complet
given_namePrénom
family_nameNom de famille
emailAdresse email
email_verifiedEmail vérifié (booléen)
pictureURL de la photo de profil
localeLangue préférée

Recevoir un ID Token ne prouve rien : votre redirect_uri est une URL publique, n'importe qui peut y poster une chaîne de caractères qui ressemble à un JWT. La validation est donc le seul mécanisme qui transforme une donnée entrante en preuve d'authentification. Les six contrôles décrits ici ne sont pas des options à cocher selon le niveau de risque : ils forment un ensemble, et en retirer un suffit à ouvrir une faille exploitable. En pratique, utilisez une bibliothèque OIDC certifiée plutôt qu'un décodeur JWT générique, car la seconde catégorie ne fait aucun de ces contrôles par défaut.

L'ordre de ces étapes compte. La signature se vérifie en premier : tant qu'elle n'est pas confirmée, les autres claims sont des chaînes de caractères sans valeur, y compris le kid du header. Les étapes 2 à 4 répondent chacune à une question différente : iss répond « qui a émis ce jeton », aud répond « m'était-il destiné », exp répond « est-il encore vivant ». Le nonce et le contrôle de l'algorithme ferment les deux angles d'attaque les plus classiques, le rejeu d'un ancien jeton et la signature désactivée.

  1. Vérifier la signature

    Récupérez les clés publiques via JWKS et vérifiez la signature. Utilisez le kid (Key ID) du header pour trouver la bonne clé.

  2. Vérifier l'issuer (iss)

    Doit correspondre exactement à votre OpenID Provider attendu. Une différence = token d'un autre IdP.

  3. Vérifier l'audience (aud)

    Doit contenir votre client_id. Un token valide pour une autre app n'est pas valide pour vous.

  4. Vérifier l'expiration (exp)

    Le token ne doit pas être expiré. Comparez avec l'heure actuelle (attention aux fuseaux horaires).

  5. Vérifier le nonce

    Si vous avez envoyé un nonce à l'autorisation, vérifiez qu'il correspond. Protection contre les attaques de replay.

  6. Vérifier l'algorithme (alg)

    Refusez alg: none, signature désactivée. N'acceptez que les algorithmes attendus (RS256, ES256...).

Cet exemple s'appuie sur jsonwebtoken pour la vérification et jwks-rsa pour aller chercher la clé publique. Deux détails font tout le travail de sécurité. Le paramètre algorithms est une liste blanche explicite : sans lui, la bibliothèque accepterait l'algorithme annoncé dans le header, donc celui choisi par l'attaquant. Et les options issuer et audience passées à jwt.verify déclenchent les contrôles correspondants ; les omettre revient à ne vérifier que la signature, ce qui laisse passer un jeton valide émis pour une autre application. Le nonce, lui, n'est pas géré par la bibliothèque : c'est à vous de le comparer à la valeur stockée en session.

import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
const client = jwksClient({
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
cache: true,
cacheMaxAge: 600000, // 10 minutes
});
// expectedNonce provient de la session utilisateur, pas du token
async function validateIdToken(token, expectedNonce) {
// 1. Décoder le header pour obtenir le kid
const decoded = jwt.decode(token, { complete: true });
if (!decoded) throw new Error('Invalid token format');
// 2. Récupérer la clé publique
const key = await client.getSigningKey(decoded.header.kid);
const publicKey = key.getPublicKey();
// 3. Vérifier le token complet
const verified = jwt.verify(token, publicKey, {
issuer: 'https://auth.example.com',
audience: 'my-app-client-id',
algorithms: ['RS256', 'ES256'], // Whitelist explicite
});
// 4. Vérifier le nonce (si utilisé)
if (verified.nonce !== expectedNonce) {
throw new Error('Nonce mismatch');
}
return verified;
}

Ces deux jetons arrivent dans la même réponse du token endpoint, ce qui explique la confusion. Ils n'ont pourtant ni le même destinataire ni le même rôle : l'ID Token s'arrête chez le client qui l'a demandé, l'Access Token voyage vers les APIs. La ligne à regarder en premier dans le tableau est « consommé par » ; toutes les autres différences en découlent.

AspectID TokenAccess Token
Consommé parLe client (Relying Party)L'API (Resource Server)
ContenuIdentité de l'utilisateurPermissions (scopes)
Question"Qui est connecté ?""Quels accès sont autorisés ?"
FormatToujours JWTJWT ou opaque
Durée de vieCourte (minutes)Courte (minutes)
Envoyé à l'API❌ Non✅ Oui

Les OpenID Providers publient leur configuration à une URL standardisée :

https://auth.example.com/.well-known/openid-configuration

Ce document JSON est la carte d'identité technique du fournisseur. Trois champs conditionnent votre configuration : jwks_uri indique où récupérer les clés de vérification, id_token_signing_alg_values_supported donne la liste blanche d'algorithmes à recopier dans votre validation, et issuer doit correspondre caractère pour caractère au iss que vous attendrez dans les jetons. Un issuer qui ne correspond pas à l'URL depuis laquelle vous avez chargé ce document est un signal d'alerte : la spécification Discovery impose cette égalité.

{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/authorize",
"token_endpoint": "https://auth.example.com/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/.well-known/jwks.json",
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"response_types_supported": ["code", "id_token", "token"],
"id_token_signing_alg_values_supported": ["RS256", "ES256"],
"claims_supported": ["sub", "name", "email", "email_verified"]
}

L'intérêt concret de Discovery se mesure le jour où le fournisseur d'identité change quelque chose. Sans lui, vous avez recopié cinq URLs dans un fichier de configuration par application, et chaque migration devient une opération coordonnée. Avec lui, vous ne configurez qu'une seule valeur, l'URL de l'issuer, et la bibliothèque cliente résout le reste au démarrage.

  • Pas de configuration manuelle : l'application découvre les endpoints automatiquement
  • Rotation transparente : les URLs peuvent changer sans casser les clients
  • Validation de l'issuer : vérifiez que l'issuer Discovery correspond à celui attendu

Le JWKS (JSON Web Key Set) contient les clés publiques pour vérifier les signatures des tokens.

L'appel se fait en HTTP GET non authentifié : ces clés sont publiques par nature, elles ne servent qu'à vérifier une signature, jamais à en produire une. La réponse contient volontairement plusieurs clés. Pendant une rotation, le fournisseur publie la nouvelle clé avant de commencer à s'en servir, et conserve l'ancienne le temps que les jetons déjà émis expirent. C'est le champ kid qui permet à votre code de choisir la bonne dans le lot.

GET https://auth.example.com/.well-known/jwks.json
{
"keys": [
{
"kid": "key-2024-02",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "0vx7agoebG...",
"e": "AQAB"
},
{
"kid": "key-2024-01",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "abc123...",
"e": "AQAB"
}
]
}

Les clés de signature changent régulièrement (sécurité, compromission). Votre application doit :

  1. Cacher les clés JWKS avec un TTL raisonnable (5-15 minutes)

  2. Rafraîchir si un kid inconnu apparaît dans un token

  3. Valider que le nouveau kid provient bien du bon endpoint JWKS

Le UserInfo endpoint retourne des informations supplémentaires sur l'utilisateur authentifié.

Fenêtre de terminal
curl -H "Authorization: Bearer <access_token>" \
https://auth.example.com/userinfo

Réponse :

{
"sub": "user-123",
"name": "Alice Martin",
"email": "alice@example.com",
"email_verified": true,
"picture": "https://example.com/photos/alice.jpg"
}

La question que ce tableau tranche est : où lire le profil de l'utilisateur ? La réponse dépend de la fraîcheur dont vous avez besoin. L'ID Token est une photo prise à l'instant du login, il ne bougera plus jusqu'à la prochaine authentification ; si l'utilisateur change son nom dans l'annuaire, votre application continuera d'afficher l'ancien. Le UserInfo endpoint interroge le fournisseur en direct et reflète l'état courant, au prix d'un appel réseau à chaque fois. En pratique, on lit l'ID Token au login, puis on rafraîchit via UserInfo à la demande.

AspectID TokenUserInfo
DisponibleAu moment du loginÀ tout moment avec un access token valide
FormatJWT signéJSON simple
FraîcheurFigé au moment de l'émissionPeut refléter des changements récents
UsageAuthentification initialeMise à jour du profil
SessionGérée parDurée
Session IdPL'OpenID ProviderLongue (heures/jours)
Session applicationVotre applicationConfigurable

Ces sessions sont indépendantes. La fin de l'une n'implique pas automatiquement la fin de l'autre.

Le SSO n'est pas une fonctionnalité qu'on active, c'est une conséquence directe de la session IdP décrite au-dessus. Rien ne change dans le protocole : votre application déclenche exactement la même redirection que d'habitude, mais le fournisseur reconnaît le cookie de session déjà posé sur son propre domaine et renvoie les jetons sans afficher de formulaire. Du point de vue de l'utilisateur, l'écran de login clignote à peine. Vous pouvez d'ailleurs forcer le comportement inverse avec le paramètre prompt=login, qui exige une réauthentification même si la session existe, réflexe utile avant une opération sensible.

Si l'utilisateur a déjà une session active sur l'IdP :

  1. Votre app redirige vers l'IdP
  2. L'IdP reconnaît l'utilisateur (session existante)
  3. L'IdP redirige immédiatement vers votre app avec les tokens
  4. Pas de login demandé

Pour déconnecter l'utilisateur de toutes les applications :

  1. L'application appelle le end_session_endpoint de l'IdP
  2. L'IdP invalide sa session
  3. L'IdP notifie les autres applications (front-channel ou back-channel logout)
// Logout OIDC
const logoutUrl = new URL('https://auth.example.com/logout');
logoutUrl.searchParams.set('id_token_hint', idToken);
logoutUrl.searchParams.set('post_logout_redirect_uri', 'https://myapp.com/logged-out');
window.location.href = logoutUrl.toString();

Avec des tokens JWT, vous pouvez avoir des sessions stateless :

  • Pas de session serveur à maintenir
  • Le token contient toutes les informations
  • Problème : révocation difficile (attendre l'expiration)

Mitigation : tokens courts (15-30 min) + refresh tokens révocables côté serveur.

Les scopes que vous envoyez à l'autorisation déterminent les claims que vous recevrez en retour. Le premier de la liste n'est pas facultatif : sans openid dans la requête, le fournisseur traite l'échange comme du OAuth 2.0 pur et ne renvoie aucun ID Token. C'est l'erreur de débutant la plus fréquente, et le symptôme est déroutant puisque l'authentification semble réussir. Pour le reste, appliquez le moindre privilège : ne demandez profile ou email que si votre application en a un usage réel, chaque scope superflu allonge l'écran de consentement et élargit la surface de données collectées.

ScopeClaims retournés
openidsub (obligatoire pour OIDC)
profilename, family_name, given_name, picture, locale, etc.
emailemail, email_verified
addressaddress (objet structuré)
phonephone_number, phone_number_verified
offline_accessObtenir un refresh token

Voici l'enchaînement complet du flow Authorization Code avec PKCE, celui que la spécification recommande aujourd'hui pour toutes les applications, y compris celles qui disposent d'un secret client. Trois valeurs aléatoires circulent en parallèle des jetons et jouent chacune un rôle distinct : le state protège du CSRF sur l'URL de retour, le nonce lie l'ID Token à votre requête initiale, et le code_verifier de PKCE empêche qu'un code d'autorisation intercepté soit échangeable par un tiers. Générez-les de façon cryptographiquement aléatoire et stockez-les côté session, jamais dans l'URL.

Le moment clé est l'étape 6 : l'échange du code se fait en POST direct entre votre serveur et le fournisseur, hors du navigateur. C'est ce canal arrière qui rend le flow sûr, puisque les jetons ne transitent jamais par la barre d'adresse.

  1. L'utilisateur clique "Se connecter"

    L'application génère un state, un nonce, et un code_verifier (PKCE).

  2. Redirection vers l'IdP

    GET https://auth.example.com/authorize?
    response_type=code&
    client_id=my-app&
    redirect_uri=https://myapp.com/callback&
    scope=openid profile email&
    state=abc123&
    nonce=xyz789&
    code_challenge=SHA256(code_verifier)&
    code_challenge_method=S256
  3. Authentification sur l'IdP

    L'utilisateur entre ses credentials (et MFA si configuré).

  4. Consentement

    L'utilisateur accepte les scopes demandés (ou les a déjà acceptés).

  5. Retour avec le code

    GET https://myapp.com/callback?
    code=AUTH_CODE&
    state=abc123
  6. Échange du code contre les tokens

    Fenêtre de terminal
    POST https://auth.example.com/token
    Content-Type: application/x-www-form-urlencoded
    grant_type=authorization_code&
    code=AUTH_CODE&
    redirect_uri=https://myapp.com/callback&
    client_id=my-app&
    code_verifier=ORIGINAL_VERIFIER
  7. Réception des tokens

    {
    "access_token": "eyJhbGciOiJSUzI1Ni...",
    "id_token": "eyJhbGciOiJSUzI1Ni...",
    "refresh_token": "dGhpcyBpcyBhIHJlZn...",
    "token_type": "Bearer",
    "expires_in": 3600
    }
  8. Validation de l'ID Token

    L'application valide signature, iss, aud, exp, nonce.

  9. Session établie

    L'application crée une session pour l'utilisateur.

Ces cinq erreurs partagent un point commun : l'application fonctionne parfaitement malgré elles. Rien ne casse, aucun test ne devient rouge, l'utilisateur se connecte. C'est précisément ce qui les rend dangereuses, car elles ne se manifestent qu'au moment de l'exploitation. Lisez la colonne « conséquence » en priorité : les trois premières lignes décrivent des contournements complets de l'authentification, la quatrième une fuite de portée, la dernière un simple problème de performance.

ErreurConséquenceSolution
Pas de validation audToken d'une autre app acceptéToujours valider l'audience
Accepter alg: noneTokens forgés acceptésWhitelist d'algorithmes
Ignorer le nonceVulnérable au replayGénérer et valider un nonce
ID Token envoyé à l'APIMauvais usage du tokenUtiliser l'access token pour les APIs
Pas de cache JWKSLatence à chaque validationCache avec TTL de 10-15 min

OIDC est un standard, pas un logiciel. Pour l'utiliser, il vous faut un fournisseur d'identité (OpenID Provider) qui l'implémente : c'est lui qui authentifie les utilisateurs et émet les ID Tokens. Vous pouvez passer par un service managé (Google, Microsoft Entra, Auth0) ou héberger le vôtre. Voici les principaux fournisseurs open source et auto-hébergeables.

FournisseurProfilIdéal pour
KeycloakRéférence entreprise : realms, fédération, adaptateursEntreprise, SI complexe
AuthentikIdP moderne : OIDC, SAML et proxy intégrés, UI soignéeHomelab exigeant, PME
AutheliaPortail d'authentification léger + 2FA devant un reverse proxyHomelab, protéger des apps sans OIDC natif
ZitadelIAM cloud-native multi-tenant, alternative à Auth0SaaS, multi-organisation
Pocket IDFournisseur OIDC minimaliste, connexion par passkey uniquementHomelab simple, sans mot de passe
KanidmIdP moderne écrit en Rust, sûr par défautPetites infra soucieuses de sécurité
DexConnecteur OIDC fédérateur, souvent embarqué dans KubernetesClusters, fédération d'IdP existants
Ory HydraServeur OAuth2/OIDC headless certifié, sans interfaceDéveloppeurs, intégration sur mesure

Pour choisir en bref : une entreprise ou un SI complexe s'oriente vers Keycloak ou Zitadel ; un homelab vers Authentik (complet) ou Authelia (léger, devant un reverse proxy) ; un besoin sans mot de passe vers Pocket ID ; un cluster Kubernetes vers Dex ; une application headless vers Ory Hydra. À ce jour, seuls Keycloak et Authentik disposent d'un guide dédié sur ce site ; les autres sont des pistes d'implémentation selon votre contexte.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn