Tous les articles
12 septembre 202610 min de lecture

Comment fonctionne un login SSO avec OpenID Connect

Suivre un login SSO avec OpenID Connect : redirection vers l’Identity Provider, code d’autorisation, échange de tokens et création de session.

  • Identity Security
  • OIDC
  • SSO
  • IAM

Dans l’article précédent, j’ai séparé authentification et autorisation. Reste une question très concrète : comment une application arrive-t-elle réellement à savoir qui vient de se connecter ?

Avec OpenID Connect, la réponse commence souvent par quelque chose d’assez déroutant quand on vient du développement classique : l’application ne demande pas elle-même le mot de passe.

Elle envoie l’utilisateur ailleurs.

Le problème

Prenons une application interne qui possède un bouton Se connecter.

Sans fédération d’identité, l’application peut gérer elle-même les comptes, les mots de passe, leur stockage, leur récupération, le MFA et tout ce qui vient avec. Autrement dit, une quantité impressionnante de responsabilités pour afficher un formulaire avec deux champs.

Dans une architecture SSO, on déplace cette responsabilité vers un Identity Provider.

L’application ne cherche plus directement à vérifier le mot de passe. Elle veut obtenir une réponse fiable à une autre question :

Cet utilisateur a-t-il déjà été authentifié par l’Identity Provider, et quelle identité a été établie ?

OpenID Connect, ou OIDC, fournit le protocole permettant de faire cette liaison.

Le résultat visible pour l’utilisateur est familier : il clique sur Se connecter, passe éventuellement par une page de connexion centralisée, puis revient dans l’application authentifié.

Le mécanisme derrière ce petit aller-retour est plus intéressant.

Le modèle mental

Le plus simple est d’imaginer trois acteurs :

Utilisateur
    ↓
Application
    ↓
Identity Provider

L’application ne demande pas à l’utilisateur de lui prouver directement son identité.

Elle lui dit en substance :

« Va t’authentifier auprès de l’Identity Provider,
puis reviens avec une preuve que je peux vérifier. »

Dans le vocabulaire OpenID Connect :

  • l’application est le Relying Party, ou client ;
  • le système d’identité est l’OpenID Provider ;
  • l’utilisateur est l’End-User.

Dans une architecture réelle, une API peut ensuite entrer dans la chaîne comme Resource Server, mais elle n’est pas nécessaire pour comprendre le login lui-même.

Le flux que je garde en tête est donc :

Application
    ↓ redirection
Identity Provider
    ↓ authentification
Navigateur
    ↓ code
Application
    ↓ échange serveur
Identity Provider
    ↓ tokens
Application
    ↓
Session locale

Le point important est là : le navigateur transporte le code d’autorisation, mais l’application échange ensuite ce code contre les tokens.

Comment ça fonctionne

Pour un login web moderne, le flux courant est l’Authorization Code Flow, généralement accompagné de PKCE.

OpenID Connect s’appuie sur OAuth 2.0 et ajoute la couche d’identité. Une requête OIDC se reconnaît notamment à la présence du scope openid.

1. L’utilisateur démarre la connexion

L’utilisateur clique sur Se connecter.

L’application génère alors une requête vers l’endpoint d’autorisation de l’Identity Provider.

Elle contient notamment des informations comme :

client_id
redirect_uri
response_type=code
scope=openid ...
state

Avec PKCE, elle ajoute également un code_challenge.

Le navigateur est ensuite redirigé vers l’Identity Provider.

2. L’Identity Provider authentifie l’utilisateur

À ce moment-là, l’utilisateur n’est plus sur la page de login de l’application.

Il se trouve chez l’Identity Provider.

C’est lui qui décide comment authentifier la personne : mot de passe, passkey, MFA, certificat ou autre méthode selon sa configuration.

C’est une frontière de sécurité importante : le mot de passe n’a pas besoin de transiter par l’application cliente.

Si l’utilisateur possède déjà une session active chez l’Identity Provider, cette étape peut même devenir presque invisible. L’IdP reconnaît l’utilisateur et peut immédiatement poursuivre le flux.

C’est là que l’expérience Single Sign-On apparaît : plusieurs applications peuvent déléguer leur authentification au même système d’identité et profiter de la session que celui-ci possède déjà.

3. L’Identity Provider renvoie un code

Une fois l’utilisateur authentifié, l’Identity Provider ne renvoie pas directement tous les tokens dans l’URL.

Dans l’Authorization Code Flow, il redirige le navigateur vers la redirect_uri enregistrée pour l’application avec un authorization code.

Schématiquement :

https://application.example/callback?code=abc123&state=...

Ce code est temporaire et destiné à être échangé.

La redirect_uri a ici une vraie fonction de sécurité : l’Identity Provider ne doit pas envoyer le résultat du login vers une destination arbitraire choisie par l’appelant.

4. L’application échange le code contre des tokens

L’application reçoit le callback puis contacte directement le token endpoint de l’Identity Provider.

Elle présente le code reçu.

Avec PKCE, elle fournit aussi le code_verifier correspondant au code_challenge envoyé au début du flux. L’objectif est d’empêcher qu’un code intercepté soit réutilisé par quelqu’un qui ne possède pas ce verifier.

Pour un client confidentiel, l’application peut également s’authentifier auprès du token endpoint avec ses propres credentials, selon sa configuration.

L’Identity Provider vérifie la demande puis renvoie les tokens prévus par le flux.

Dans un login OpenID Connect, on retrouve notamment un ID token, qui porte des informations permettant au client d’établir l’identité authentifiée.

Un access token peut également être émis pour accéder à une ressource protégée.

Je garde volontairement la distinction entre les différents tokens pour l’article suivant. Sinon cet article finirait par devenir trois articles empilés dans un trench-coat.

5. L’application crée sa propre session

Une fois l’identité établie, l’application crée généralement une session locale.

C’est cette session qui permet ensuite aux requêtes suivantes de savoir que l’utilisateur est connecté sans recommencer tout le flux OIDC à chaque page.

Il faut donc distinguer deux choses :

Session chez l’Identity Provider
≠
Session dans l’application

Elles sont liées, mais ce ne sont pas le même objet.

Cette distinction explique plusieurs comportements qui semblent étranges au premier abord, notamment les reconnexions immédiates après un simple logout local.

Exemple concret

Alice utilise deux applications internes : une plateforme RH et un outil de gestion des dépenses.

Les deux utilisent le même Identity Provider.

Le matin, Alice ouvre l’application RH.

1. RH redirige Alice vers l’IdP
2. Alice s’authentifie
3. L’IdP renvoie un code
4. RH échange le code
5. RH crée sa session

Quelques minutes plus tard, Alice ouvre l’outil de dépenses.

L’application la redirige elle aussi vers l’Identity Provider.

Mais celui-ci possède déjà une session pour Alice.

Il n’a donc pas nécessairement besoin de lui demander à nouveau son mot de passe ou son MFA. Il peut renvoyer directement un résultat d’authentification à la deuxième application.

Pour Alice, cela ressemble à une connexion automatique.

Pour les applications, il y a pourtant bien eu deux sessions applicatives distinctes.

Le SSO tient à une seule chose : les applications s’appuient sur la même session d’authentification chez l’Identity Provider. Aucune session n’est directement partagée entre elles.

Dans mon lab

J’ai reproduit ce flux dans mon Identity Security Lab avec :

  • Keycloak comme OpenID Provider ;
  • Next.js comme application cliente ;
  • Auth.js pour l’intégration OIDC ;
  • une API Express séparée comme resource server.

Le client Keycloak utilisé par le frontend s’appelle identity-lab-web.

La configuration Auth.js ne contient pas une collection d’URLs d’authentification écrites à la main. Elle fournit principalement :

clientId
clientSecret
issuer

À partir de l’issuer, le client peut s’appuyer sur le document de découverte OpenID Connect publié par Keycloak :

/realms/identity-lab/.well-known/openid-configuration

Ce document expose notamment l’authorization endpoint, le token endpoint et les autres métadonnées nécessaires au client.

Le login suit ensuite le chemin suivant :

Navigateur
    ↓
Next.js / Auth.js
    ↓
Keycloak /auth
    ↓
Page de connexion Keycloak
    ↓
Callback Next.js avec authorization code
    ↓
Échange du code auprès de Keycloak /token
    ↓
Tokens
    ↓
Session Auth.js

Dans ce lab, le frontend est configuré comme client confidentiel. L’échange du code se fait côté serveur avec le client secret.

Le flux utilise également PKCE.

Conséquence pratique importante : lorsque l’utilisateur saisit son mot de passe sur la page Keycloak, le mot de passe ne passe pas par mon application Next.js.

C’est précisément ce que je voulais voir fonctionner plutôt que simplement le lire dans une documentation.

J’ai aussi choisi de ne pas exposer les tokens directement au JavaScript du navigateur. Auth.js conserve les informations de session dans son mécanisme serveur et le refresh token n’est pas renvoyé dans l’objet de session accessible au client.

Autre détail intéressant : le logout.

Supprimer uniquement la session de l’application ne supprime pas automatiquement la session SSO chez Keycloak. Si je détruis la session locale puis que je relance immédiatement un login, Keycloak peut encore reconnaître l’utilisateur et le reconnecter sans lui redemander son mot de passe.

Pour provoquer une vraie déconnexion SSO dans le lab, l’application appelle donc également l’end_session_endpoint de Keycloak.

Cette expérience rend la séparation beaucoup plus claire :

login applicatif
≠
session SSO de l’Identity Provider

Ce qui peut mal tourner

La redirect URI ne correspond pas

Symptôme : l’utilisateur arrive bien chez l’Identity Provider, mais le flux s’arrête avec une erreur de redirection.

Premier contrôle : comparer la redirect_uri réellement envoyée par le client avec les URIs autorisées dans la configuration de l’application.

Une différence de schéma, de port, de chemin ou parfois simplement d’environnement suffit à casser le callback.

L’issuer n’est pas celui attendu

Le client OIDC découvre sa configuration à partir de l’issuer.

Une mauvaise URL de realm, un hostname différent entre l’intérieur et l’extérieur du réseau ou une configuration incorrecte derrière un reverse proxy peuvent produire des URLs incohérentes et casser le flux ou la validation des tokens.

Le diagnostic doit donc commencer par le document :

/.well-known/openid-configuration

Il permet de voir ce que l’Identity Provider annonce réellement.

Le state ne correspond plus

Le paramètre state permet notamment de lier la réponse au flux initié par le client et participe à la protection contre les attaques de type CSRF.

Si l’état renvoyé ne correspond pas à celui attendu, le client doit refuser le callback.

Contourner ce contrôle pour « faire marcher le login » serait une façon assez créative de réparer une alarme incendie en retirant ses piles.

Le code ne peut pas être échangé

Recevoir un authorization code ne signifie pas encore que le login est terminé.

L’échange auprès du token endpoint peut échouer : code expiré ou déjà utilisé, mauvais redirect_uri, problème d’authentification du client ou vérification PKCE impossible.

La bonne question devient alors : le problème est-il avant le callback, ou pendant l’échange du code ?

Cette séparation réduit énormément le champ de recherche.

Le logout n’est que local

Dernier piège classique : l’application détruit son cookie mais laisse la session de l’Identity Provider intacte.

L’utilisateur clique de nouveau sur Se connecter et revient immédiatement dans l’application.

Ce n’est pas forcément un bug OIDC. Le SSO fonctionne précisément comme prévu.

Si l’objectif est une déconnexion globale, il faut aussi traiter la session côté Identity Provider.

À retenir

  • OpenID Connect permet à une application de déléguer l’authentification à un Identity Provider.
  • L’application redirige d’abord le navigateur vers l’IdP au lieu de collecter elle-même le mot de passe.
  • Dans l’Authorization Code Flow, l’IdP renvoie un code temporaire, puis l’application échange ce code auprès du token endpoint.
  • Le scope openid transforme le flux en requête OpenID Connect et permet au client d’obtenir une identité authentifiée.
  • Le SSO vient de la session existante chez l’Identity Provider, pas d’un cookie applicatif magiquement partagé entre toutes les applications.
  • Une session applicative et une session SSO sont deux objets distincts.
  • Pour diagnostiquer un login OIDC, il faut suivre le flux dans l’ordre : redirection, authentification, callback, échange du code, création de session.

À ce stade, le chemin du login est posé. La prochaine question est ce que l’Identity Provider remet réellement à l’application après cet échange : ID token, access token et refresh token n’ont pas le même rôle.