1. Objectif & périmètre
Permettre à un utilisateur de se connecter à votre application avec son compte Google (Gmail / Google Workspace) ou Microsoft (compte professionnel/scolaire Entra ID, ou compte personnel), sans créer de nouveau mot de passe — c'est le SSO (Single Sign-On).
Le standard à utiliser est OpenID Connect (OIDC), une couche d'authentification posée au-dessus d'OAuth 2.0 (qui gère l'autorisation). Google et Microsoft sont tous deux des fournisseurs OIDC conformes : la même mécanique s'applique aux deux, seuls les paramètres de configuration changent.
Principe directeur
Ne réécrivez jamais la cryptographie ni la validation de jetons vous-même. Utilisez une bibliothèque OIDC certifiée (openid-client, Authlib, Microsoft.Identity.Web, Spring Security…) ou un broker d'identité (Keycloak, Auth0/Okta, Microsoft Entra External ID). Ce guide vous donne le vocabulaire et les bonnes configurations — la bibliothèque fait le reste.
2. Vocabulaire indispensable
| Terme | Signification |
|---|---|
| IdP (Identity Provider) | Le fournisseur d'identité : Google, Microsoft Entra ID. |
| RP / Client | Votre application (Relying Party), qui « fait confiance » à l'IdP. |
| ID token | Un JWT signé qui prouve qui est l'utilisateur (authentification). Pièce maîtresse du SSO. |
| Access token | Jeton d'autorisation pour appeler une API. Ne sert pas à authentifier. |
| Refresh token | Jeton permettant d'obtenir de nouveaux tokens sans réauthentification. |
| Scopes | Périmètre demandé. Pour le SSO : openid email profile. |
| Claims | Infos sur l'utilisateur dans l'ID token (sub, email, name, email_verified…). |
| redirect_uri | URL de votre app où l'IdP renvoie l'utilisateur. Pré-enregistrée à l'identique. |
| state | Valeur aléatoire anti-CSRF, vérifiée au retour. |
| nonce | Valeur aléatoire anti-rejeu, liée à l'ID token. |
| PKCE | Protection du code d'autorisation (RFC 7636). Obligatoire pour clients publics, recommandée pour tous. |
| Discovery document | URL standard /.well-known/openid-configuration listant endpoints et clés de l'IdP. |
| JWKS | Le jeu de clés publiques de l'IdP servant à vérifier la signature des jetons. |
3. Choisir son approche
3.1 Type d'application (détermine la sécurité)
Client confidentiel (web back-end)
Le back-end garde un secret. Utilise un client_secret (ou un certificat). Échange le code côté serveur. Cas le plus fréquent.
Client public (SPA / mobile / desktop)
Pas de secret (il serait exposé). PKCE est obligatoire. La bibliothèque OIDC le gère pour vous.
3.2 Intégration directe ou broker ?
Bibliothèque OIDC par fournisseur
Recommandé pour démarrer simple. Google et Microsoft intégrés comme deux fournisseurs OIDC derrière une couche commune dans votre app.
Broker d'identité (Keycloak, Auth0, Entra External ID…)
Un seul point d'intégration. Le broker prend en charge Google, Microsoft, SAML, MFA, sessions. Indispensable dès que vous avez plusieurs IdP, plusieurs apps, ou des besoins entreprise (MFA, provisioning SCIM, audit).
Quel que soit le choix, traitez Google et Microsoft de façon homogène : deux boutons « Se connecter avec… » qui déclenchent le même flux OIDC avec des paramètres différents.
4. Le flux recommandé : Authorization Code + PKCE (+ OIDC)
C'est le flux standard et sûr pour le SSO web. Six temps :
Clic utilisateur
L'utilisateur clique « Se connecter avec Google » ou « avec Microsoft ».
Redirection vers l'IdP
Votre app génère state, nonce et un couple PKCE (code_verifier secret + code_challenge), puis redirige le navigateur vers l'endpoint d'autorisation de l'IdP.
Authentification chez l'IdP
L'utilisateur s'authentifie chez l'IdP et consent aux scopes demandés.
Retour avec un code
L'IdP renvoie le navigateur vers votre redirect_uri avec un code d'autorisation à usage unique + le state.
Échange du code
Votre back-end vérifie le state, puis échange le code contre des jetons à l'endpoint de token (en envoyant le code_verifier, et le secret si client confidentiel).
Validation + session
Votre app valide l'ID token, extrait l'identité, crée/relie le compte local, ouvre une session applicative.
Exemple d'URL d'autorisation (paramètres clés, communs aux deux IdP) :
GET https://<endpoint_authorize_de_l_IdP>
?client_id=VOTRE_CLIENT_ID
&response_type=code
&redirect_uri=https://votre-app.example.com/auth/callback
&scope=openid email profile
&state=ALEA_ANTI_CSRF
&nonce=ALEA_ANTI_REJEU
&code_challenge=BASE64URL_SHA256_DU_VERIFIER
&code_challenge_method=S256
5. Mise en place côté Google
- Aller dans Google Cloud Console → créer/sélectionner un projet.
- APIs & Services → OAuth consent screen : configurer l'écran de consentement (interne si Workspace, ou externe), nom, e-mail de support, domaines autorisés.
- APIs & Services → Credentials → Create credentials → OAuth client ID : type Application Web.
- Renseigner les Authorized redirect URIs (ex.
https://votre-app.example.com/auth/callback) — exactes, en HTTPS. - Récupérer le Client ID et le Client secret.
Discovery : https://accounts.google.com/.well-known/openid-configuration
Authorize : https://accounts.google.com/o/oauth2/v2/auth
Token : https://oauth2.googleapis.com/token
JWKS : https://www.googleapis.com/oauth2/v3/certs
UserInfo : https://openidconnect.googleapis.com/v1/userinfo
Issuer : https://accounts.google.com
Scopes SSO : openid email profile. Claims utiles : sub (identifiant stable), email, email_verified, name, picture, hd (domaine Workspace).
6. Mise en place côté Microsoft (Entra ID)
- Aller dans le Microsoft Entra admin center → App registrations → New registration.
- Supported account types : choisir selon le besoin — Single tenant (uniquement votre organisation), Multitenant (toutes les organisations Microsoft), Multitenant + comptes personnels (+ comptes Microsoft grand public). Ce choix détermine la valeur
{tenant}des endpoints. - Authentication → Redirect URIs : ajouter votre URI (plateforme Web), en HTTPS, à l'identique.
- Certificates & secrets : créer un client secret (copier la Value, pas le Secret ID ; définir une expiration et prévoir la rotation). Préférer un certificat au secret si possible.
- Récupérer l'Application (client) ID depuis la page Overview.
Discovery : https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
Authorize : https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
Token : https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
JWKS : https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys
UserInfo : https://graph.microsoft.com/oidc/userinfo
{tenant} = common (tous) / organizations (pro/scolaire) / consumers (perso) / ID de tenant précis. Scopes SSO : openid email profile. Claims utiles : sub, oid (utilisateur stable dans le tenant), tid (tenant), email / preferred_username.
⚠️ Piège multi-tenant Microsoft
L'issuer contient un identifiant de tenant variable. Si vous ne validez pas explicitement les tid / iss autorisés, n'importe quel tenant Microsoft pourra se connecter à votre app — il suffit qu'un attaquant crée un tenant Entra gratuit. Toujours maintenir une allowlist explicite des tid autorisés côté serveur.
7. Côté application : les étapes d'implémentation
Initier
Générer state, nonce, PKCE ; stocker temporairement (cookie signé / serveur) ; rediriger vers l'endpoint d'autorisation.
Callback
Vérifier que le state reçu correspond ; échanger le code contre les jetons à l'endpoint de token (avec code_verifier, et le secret si confidentiel).
Valider l'ID token
Côté serveur, impérativement : signature via le JWKS, iss attendu, aud = votre client_id, exp / iat valides (tolérance d'horloge ~ quelques minutes), nonce = celui envoyé.
Identité & compte
Lire les claims, relier au compte local par e-mail vérifié (email_verified = true) ou par sub / oid ; créer le compte au besoin ; ouvrir la session applicative.
Déconnexion
Invalider la session locale ; optionnellement déclencher la déconnexion côté IdP via l'end_session_endpoint.
8. Sécurité — points non négociables
Tout est de votre responsabilité, pas de celle du fournisseur
La sécurité d'une intégration SSO se joue entièrement côté votre application. L'IdP fait son travail si le vôtre est correct.
- HTTPS partout. Aucune exception, y compris en pré-prod.
redirect_uriexacte et pré-enregistrée : pas de wildcard, attention à la casse et au slash final (correspondance stricte).state(anti-CSRF) +nonce(anti-rejeu) systématiques, et PKCE même pour les clients confidentiels.- Validation de l'ID token côté serveur (signature,
iss,aud,exp,nonce) — ne jamais faire confiance à un jeton non vérifié. - Secrets côté serveur uniquement : jamais dans un SPA, une app mobile ou un dépôt Git. Coffre, rotation, expiration. Côté Microsoft, préférer un certificat au secret.
- Sessions/jetons : cookies
HttpOnly+Secure+SameSite, pas danslocalStorage. Sessions courtes, rotation des refresh tokens. - Liaison de comptes par e-mail vérifié uniquement (
email_verified). Sinon, risque d'usurpation de compte. - Multi-tenant Microsoft : valider
tid/issautorisés. - Minimiser les scopes (
openid email profilesuffit ; ne demandez des scopes API qu'en cas de besoin réel). - JWKS : mise en cache + gestion de la rotation des clés de l'IdP.
- Journalisation des connexions/échecs, anti-bruteforce, et révocation des accès à la sortie d'un collaborateur.
9. Les 6 pièges les plus fréquents
1 — Authentifier avec l'access token
L'access token est un jeton d'autorisation, pas d'authentification. L'ID token est l'unique source de vérité pour le SSO.
2 — redirect_uri non exacte
Slash, casse, http/https : la correspondance est stricte. Une virgule de plus → erreur de redirection.
3 — Pas de validation de tenant
En multi-tenant Microsoft, sans allowlist tid / iss, n'importe quel tenant Microsoft se connecte à votre app.
4 — Secret exposé dans un client public
Un secret dans un SPA, une app mobile ou un dépôt Git, c'est un secret public. Utilisez PKCE sans secret.
5 — Liaison sur e-mail non vérifié
Sans email_verified = true, n'importe qui peut créer un compte au nom d'un autre chez un IdP permissif et prendre le contrôle.
6 — Secret Microsoft expiré
Faute de rotation planifiée, l'expiration arrive en pleine prod. Plusieurs secrets actifs en parallèle = bascule sans coupure.
10. Bibliothèques recommandées (selon la stack)
| Stack | Bibliothèque |
|---|---|
| Node.js | openid-client (certifiée OIDC), ou Passport avec stratégies Google/Microsoft |
| .NET / ASP.NET Core | Microsoft.Identity.Web, middleware OpenIdConnect |
| Java / Spring | Spring Security — OAuth2 Client |
| Python | Authlib (ou python-social-auth) |
| PHP | league/oauth2-client + providers Google/Microsoft |
| Brokers / passerelles | Keycloak, Authelia (open source) ; Auth0/Okta, Microsoft Entra External ID (managés) |
11. Checklist de mise en production
Avant le go-live
Cochez toutes les cases. Tester avec un compte « cobaye » sur chaque branche de la matrice (nouveau / existant / refus consentement / token expiré / e-mail non vérifié / mauvais tenant).
- Clients OAuth créés côté Google et Microsoft,
redirect_uride prod enregistrées (HTTPS, exactes) - Secrets en coffre, rotation planifiée, échéances suivies (Microsoft)
- PKCE +
state+nonceactivés - Validation complète de l'ID token (signature/JWKS,
iss,aud,exp,nonce) - Tenant Microsoft validé (si multi-tenant) ; e-mail vérifié exigé pour la liaison de comptes
- Sessions en cookies
HttpOnly/Secure/SameSite, durées courtes, déconnexion fonctionnelle - Scopes minimaux ; journalisation et procédure de révocation en place
- Tests : nouveau compte, compte existant, refus de consentement, jeton expiré, e-mail non vérifié, mauvais tenant
12. Exemples de code commentés
Lecture des exemples
L'essentiel (PKCE, state, nonce, validation de l'ID token) est délégué à une bibliothèque certifiée. Le même code sert pour Google et Microsoft — seuls l'issuer et les identifiants changent. Adaptez selon la version de la bibliothèque.
12.1 Node.js — Express + openid-client
// SSO OIDC avec Google et Microsoft — Node.js (Express + openid-client v5)
// npm i express express-session openid-client
import express from 'express';
import session from 'express-session';
import { Issuer, generators } from 'openid-client';
const app = express();
app.use(session({
secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false,
cookie: { httpOnly: true, secure: true, sameSite: 'lax' }, // cookie de session sécurisé
}));
const REDIRECT = 'https://votre-app.example.com/auth/callback';
// 1) Découverte automatique des endpoints + création du client
async function makeClient(issuerUrl, clientId, clientSecret, redirectUri) {
const issuer = await Issuer.discover(issuerUrl);
return new issuer.Client({
client_id: clientId,
client_secret: clientSecret, // côté serveur UNIQUEMENT (client confidentiel)
redirect_uris: [redirectUri],
response_types: ['code'],
});
}
const clients = {};
(async () => {
// Google
clients.google = await makeClient('https://accounts.google.com',
process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, REDIRECT + '/google');
// Microsoft (tenant = ID de tenant, 'organizations' ou 'common')
clients.microsoft = await makeClient('https://login.microsoftonline.com/<tenant>/v2.0',
process.env.MS_CLIENT_ID, process.env.MS_CLIENT_SECRET, REDIRECT + '/microsoft');
})();
// 2) Démarrage du login : PKCE + state + nonce, stockés en session, puis redirection
app.get('/login/:idp', (req, res) => {
const client = clients[req.params.idp];
const code_verifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(code_verifier);
const state = generators.state();
const nonce = generators.nonce();
req.session.oidc = { idp: req.params.idp, code_verifier, state, nonce };
res.redirect(client.authorizationUrl({
scope: 'openid email profile',
code_challenge, code_challenge_method: 'S256', state, nonce,
}));
});
// 3) Callback : openid-client vérifie state et VALIDE l'ID token (signature/iss/aud/exp/nonce)
app.get('/auth/callback/:idp', async (req, res) => {
const client = clients[req.params.idp];
const { code_verifier, state, nonce } = req.session.oidc || {};
const params = client.callbackParams(req);
const tokenSet = await client.callback(
client.metadata.redirect_uris[0], params,
{ code_verifier, state, nonce }, // tout est vérifié pour vous
);
const claims = tokenSet.claims(); // contenu VALIDÉ de l'ID token
// 4) Liaison de compte par e-mail VÉRIFIÉ uniquement
if (!claims.email || claims.email_verified === false) {
return res.status(403).send('E-mail non vérifié par le fournisseur.');
}
const user = await findOrCreateUser({
provider: req.params.idp, subject: claims.sub, email: claims.email, name: claims.name,
});
// 5) Session applicative (aucun token stocké côté navigateur)
req.session.userId = user.id;
delete req.session.oidc;
res.redirect('/');
});
app.get('/logout', (req, res) => req.session.destroy(() => res.redirect('/')));
Versions de openid-client
Exemple en API openid-client v5. La v6 expose une API fonctionnelle différente — la logique (découverte, PKCE, validation) reste la même.
12.2 Python — Flask + Authlib
# SSO OIDC avec Google et Microsoft — Python (Flask + Authlib)
# pip install Flask Authlib
import os
from flask import Flask, session, redirect, url_for, abort
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.secret_key = os.environ["SECRET_KEY"]
app.config.update(SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_SAMESITE="Lax")
oauth = OAuth(app)
# 1) Déclaration des fournisseurs via leur discovery document
oauth.register(
name="google",
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
client_kwargs={"scope": "openid email profile"},
)
oauth.register(
name="microsoft",
server_metadata_url="https://login.microsoftonline.com/<tenant>/v2.0/.well-known/openid-configuration",
client_id=os.environ["MS_CLIENT_ID"],
client_secret=os.environ["MS_CLIENT_SECRET"],
client_kwargs={"scope": "openid email profile"},
)
# 2) Démarrage du login : Authlib génère state + nonce + PKCE et redirige
@app.route("/login/<idp>")
def login(idp):
client = oauth.create_client(idp) or abort(404)
redirect_uri = url_for("callback", idp=idp, _external=True)
return client.authorize_redirect(redirect_uri)
# 3) Callback : échange du code + validation de l'ID token par Authlib
@app.route("/auth/callback/<idp>")
def callback(idp):
client = oauth.create_client(idp) or abort(404)
token = client.authorize_access_token() # vérifie le state, échange le code
claims = token["userinfo"] # ID token déjà validé
# 4) Liaison par e-mail vérifié uniquement
if not claims.get("email") or claims.get("email_verified") is False:
abort(403, "E-mail non vérifié par le fournisseur.")
user = find_or_create_user(provider=idp, subject=claims["sub"],
email=claims["email"], name=claims.get("name"))
# 5) Session applicative (pas de tokens stockés côté navigateur)
session["user_id"] = user.id
return redirect("/")
@app.route("/logout")
def logout():
session.clear()
return redirect("/")
Annexe — Récapitulatif des endpoints
Issuer : https://accounts.google.com
Discovery : https://accounts.google.com/.well-known/openid-configuration
Microsoft Entra ID
Discovery : https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
({tenant} = common | organizations | consumers | <tenant-id>)
Les noms de portails et écrans peuvent évoluer ; les discovery documents ci-dessus restent la source de vérité pour les endpoints, scopes et clés de chaque fournisseur.
Besoin d'un audit cybersécurité de votre intégration SSO ?
Mission MAG&Cie
Si vous voulez qu'on vérifie ensemble votre intégration SSO avant mise en production — validation tenant Microsoft, rotation des secrets, configuration cookies, journalisation, points d'audit RGPD — c'est exactement le périmètre de l'Audit de Posture Cybersécurité ou du Cyber-Audit accéléré (rapport sous 48h).
Pour aller plus loin sur la cybersécurité applicative TPE/PME, voir Audit cybersécurité TPE/PME : combien ça coûte et qu'est-ce qu'on y trouve.