Fabien Dorey
← Carnet N° 01 — — 6 min

Broker ou multi-IdP, dans une iframe

Ouvrir une appli de service client aux agents d’un prestataire, quand elle vit dans une iframe et que deux fournisseurs d’identité cohabitent. Monter notre propre broker, ou laisser l’appli gérer les deux ? Retour sur un choix par défaut, mais assumé.

Le besoin de départ tient en une phrase : permettre aux agents d’un prestataire, dont les identités vivent hors du groupe, de s’authentifier dans notre application de service client. Ce prestataire gère ses comptes dans son propre Keycloak, managé par ses soins. Nos collaborateurs, eux, sont dans Entra ID. Deux fournisseurs d’identité, une seule appli.

Jusque-là, rien d’exotique. Ce qui a compliqué l’affaire, c’est l’endroit où vit l’application : elle n’est pas ouverte dans un onglet, elle est embarquée dans l’outil de support des agents, dans une iframe.

Collaborateurs Agents prestataire Entra ID Keycloak Appli Support
Fig. 1 — Deux émetteurs, une appli, et un cadre autour

Ce que l’iframe casse

Une connexion OIDC classique repose sur des redirections : l’appli envoie le navigateur chez le fournisseur d’identité, qui le renvoie une fois l’utilisateur authentifié. Dans une iframe, cette mécanique se grippe à deux endroits.

  • Les redirections sont bloquées. Les pages de connexion refusent d’être affichées dans un cadre — c’est une protection contre le détournement de clic, et elle est légitime. Impossible, donc, de dérouler le login à l’intérieur de l’iframe.
  • Les cookies ne suivent plus. Vue du navigateur, notre appli est un contenu tiers dans la page de l’outil de support. Son cookie de session est un cookie tiers, et les navigateurs les bloquent de plus en plus.

Restait une question très concrète, à laquelle nous n’avions pas de réponse toute faite : si le login se passe ailleurs, comment faire revenir le résultat jusqu’à l’iframe ?

La cinématique retenue

La réponse tient en trois pièces : une fenêtre à part, un message, un cookie partitionné.

Iframe Popup IdP Backend 1 2 3 4 1 · Ouvre le login 2 · S’authentifie 3 · Code à usage unique 4 · Cookie partitionné
Fig. 2 — Le login sort du cadre, la session y revient

L’iframe ouvre le login dans une popup. Dans cette fenêtre, plus de cadre : les redirections vers le fournisseur d’identité fonctionnent normalement, et le backend termine le flux OIDC comme il le ferait pour n’importe quelle appli web. La popup prévient ensuite l’iframe avec postMessage, puis se ferme.

JavaScript
// Inside the iframe: open the login in a popup, then wait for its answer.
const popup = window.open(`/login/${provider}`, "login", "popup,width=480,height=640");

window.addEventListener("message", async (event) => {
  // only our own popup, on our own origin
  if (event.origin !== window.location.origin || event.source !== popup) return;

  // a one-time code travels between windows, never the tokens themselves
  await fetch("/session", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ code: event.data.code }),
  });
  startApp();
});

Ce qui transite entre les deux fenêtres n’est pas un jeton, mais une preuve à usage unique et à durée de vie très courte. L’iframe l’échange auprès du backend, qui répond en posant le cookie de session — et c’est parce que ce cookie est posé depuis l’iframe qu’il peut être partitionné :

http
Set-Cookie: __Host-session=…; Path=/; Secure; HttpOnly; SameSite=None; Partitioned

Un cookie partitionné (CHIPS) n’existe que pour le couple « notre appli, embarquée dans cet outil de support ». Il ne permet aucun pistage d’un site à l’autre, ce qui lui vaut d’être accepté là où un cookie tiers classique est refusé. Les jetons, eux, ne quittent jamais le backend.

Trois précautions vont avec :

  • Vérifier l’origine de chaque message reçu, des deux côtés. Un postMessage sans contrôle d’origine est une porte ouverte.
  • Limiter qui peut nous embarquer : l’appli n’accepte d’être affichée que dans l’outil de support (frame-ancestors), nulle part ailleurs.
  • Protéger contre le CSRF pour de bon. Avec SameSite=None, le navigateur ne fait plus ce travail à notre place.

Entra ID ne joue pas les brokers

Toute cette cinématique, il faut la faire fonctionner pour chaque fournisseur d’identité. D’où l’envie, très vite, de n’en avoir qu’un seul face à l’appli.

Le candidat naturel était Entra ID : c’est l’annuaire du groupe, et il est opéré par une vraie équipe IAM. Mais Entra ID ne sait pas se placer devant un autre fournisseur comme simple relais. Il sait fédérer un fournisseur externe, à une condition : que chaque utilisateur existe aussi chez nous, sous forme de compte invité (B2B).

C’est la solution la plus propre côté code — l’appli ne parle plus qu’à un émetteur. Le prix est ailleurs : le cycle de vie de ces comptes invités devient notre affaire. Avec une population de centre de contact et le turnover qui va avec, nous l’avons écartée.

Restaient deux options.

Option 1 : notre propre Keycloak

Un Keycloak chez nous, en identity broker : l’appli ne connaît que lui, et c’est lui qui va chercher l’utilisateur chez Entra ID ou chez le prestataire.

Les avantages sont réels :

  • Un seul flux à faire marcher dans l’iframe. La popup, le message, le cookie : tout est réglé une fois, pour un seul émetteur.
  • Une appli plus simple. Une configuration, un contrat de claims, et la fédération d’identités rangée dans le composant dont c’est le métier.
  • Réutilisable. La prochaine appli qui devra accueillir ce prestataire n’aura rien à refaire.

Le défaut tient en un mot : gouvernance. Un broker est un composant critique — haute disponibilité, base de données, montées de version, correctifs de sécurité — et un point de panne commun aux deux populations : s’il tombe, plus personne ne se connecte. Il lui faut un propriétaire.

Or nous avions justement un Keycloak dans le service. Il tournait, mais il n’était réellement opéré par personne. Chez nous, seul Entra ID est porté par l’équipe IAM. Bâtir l’accès de tout un service client sur un composant orphelin, ou monter une exploitation digne de ce nom dans le budget et les délais d’un projet : ni l’un ni l’autre n’était raisonnable.

Option 2 : l’appli gère les deux

L’application back-office parle directement aux deux fournisseurs. Pas de nouveau composant, pas de nouvelle dépendance à exploiter, et des pannes cloisonnées : un incident chez le prestataire ne bloque pas nos collaborateurs, et inversement.

Les défauts sont le miroir exact de l’option 1 :

  • Deux flux à faire marcher dans l’iframe, avec deux fournisseurs qui n’ont pas les mêmes réglages ni les mêmes interlocuteurs.
  • De la logique d’identité dans l’appli : deux configurations, deux formats de claims à normaliser.
  • Rien de réutilisable. La prochaine appli refera le même travail.

Ce que le multi-IdP demande à l’appli

Pour que ce choix reste vivable, l’essentiel est d’empêcher les deux fournisseurs de se répandre dans le code.

  • Un seul type d’utilisateur en sortie. Chaque fournisseur a son convertisseur, et les deux produisent le même objet métier : interne ou externe, rôles de l’appli. Passé la connexion, rien ne doit savoir d’où vient l’utilisateur.
  • Un identifiant composé : le couple émetteur et identifiant. Côté Entra ID, l’identifiant d’objet et le tenant plutôt que le sub, qui change d’une application à l’autre.
  • Des rôles d’application plutôt que des groupes côté Entra ID : un claim lisible, sans identifiants opaques ni limite de taille. Côté prestataire, des rôles déclarés sur le client.
  • L’autorisation reste une règle métier. Un agent du prestataire n’a pas les mêmes droits qu’un collaborateur, et ce n’est pas au fournisseur d’identité d’en décider.
  • Les jetons s’arrêtent au backend. Les services en aval sont appelés avec l’identité de l’appli et l’utilisateur normalisé, sinon chacun hérite du multi-IdP.

Ce que j’en retiens

Nous avons choisi le multi-IdP. Pas parce que c’est la meilleure architecture dans l’absolu : parce qu’il n’existait pas, côté groupe, de solution de fédération solide sur laquelle s’appuyer. À l’heure où j’écris, les tests d’intégration sont en cours, et en bonne voie.

Les deux options se valent sur le papier. Ce qui les départage n’est pas technique : c’est qui opère quoi.

  • Une équipe pour porter un broker → le broker. C’est là que la fédération doit vivre, et chaque nouvelle appli en profite.
  • Personne pour le porter → le multi-IdP dans l’appli, proprement isolé. Mieux vaut un peu de complexité dans un code qu’on maîtrise qu’un composant critique sans propriétaire.
  • Une population externe stable → les comptes invités, qui simplifient tout le reste.

Et le jour où une solution groupe existera, la migration sera courte : si l’appli ne connaît qu’un seul type d’utilisateur, remplacer deux fournisseurs par un seul ne touche que la porte d’entrée.