Aller au contenu
medium

Sécuriser une application avec Keycloak (OIDC)

14 min de lecture

Cette page montre comment brancher une application existante sur Keycloak en OpenID Connect. Vous n'écrivez plus de page de login : votre application redirige l'utilisateur vers Keycloak, qui l'authentifie et renvoie un token signé. Le guide couvre le choix du type de client, les redirect URIs, l'activation de PKCE, et la récupération d'un token machine-to-machine avec client_credentials. Public : développeur ou administrateur qui a déjà un realm Keycloak et veut y connecter une vraie application.

Ce guide est un how-to Keycloak. Si les notions de token, de scope ou de flow ne sont pas claires, lisez d'abord les concepts : OpenID Connect pour l'authentification et OAuth 2.x pour la délégation d'accès aux API. Ici, on configure.

  • Choisir le bon type de client selon votre application (SPA, backend, service)
  • Créer un client OIDC dans un realm, en console et en ligne de commande
  • Configurer les redirect URIs et web origins pour éviter les erreurs de redirection et de CORS
  • Activer PKCE pour protéger le flow d'autorisation
  • Récupérer et inspecter un token via les endpoints OIDC standard
  • Sécuriser une API sans utilisateur avec le flow client_credentials

Avant de commencer, il vous faut :

  • Une instance Keycloak fonctionnelle. Voir Installation de Keycloak.
  • Un realm applicatif créé (jamais le realm master) avec au moins un utilisateur de test. Voir Administrer Keycloak. Les exemples utilisent le realm lab et l'utilisateur alice.
  • Une application à connecter : peu importe le langage, le principe est identique. Ce qui change, c'est la bibliothèque OIDC côté application.

Un client dans Keycloak représente une application autorisée à demander des tokens. Le premier choix, celui qui conditionne toute la sécurité, est public ou confidentiel. La différence tient à un seul point : l'application est-elle capable de garder un secret ?

Type de clientPeut garder un secretApplication typiqueFlow recommandé
ConfidentielOui (code serveur)Application web avec backend, APIAuthorization Code + PKCE
PublicNon (code visible)SPA (React, Vue), application mobile, CLIAuthorization Code + PKCE
Confidentiel + service accountOuiService sans utilisateur (cron, backend-to-backend)Client Credentials

Une SPA (single-page application) ou une application mobile tourne entièrement chez l'utilisateur : son code JavaScript ou son binaire sont lisibles, donc elle ne peut pas stocker de secret. Elle est publique. À l'inverse, un backend Node, Django ou Spring garde son secret sur le serveur : il est confidentiel.

Prenons une application web classique avec un backend, servie sur https://app.exemple.fr. Après login, Keycloak doit renvoyer l'utilisateur sur une URL de rappel : https://app.exemple.fr/callback.

  1. Sélectionnez votre realm (lab) dans le sélecteur en haut à gauche, puis Clients > Create client.

  2. General settings :

    • Client type : OpenID Connect
    • Client ID : webapp-demo
  3. Capability config :

    • Client authentication : On (c'est ce qui rend le client confidentiel)
    • Authorization : Off (les permissions fines sont un autre sujet)
    • Standard flow : coché (c'est l'Authorization Code)
    • Direct access grants : décoché
  4. Login settings :

    • Valid redirect URIs : https://app.exemple.fr/callback
    • Web origins : https://app.exemple.fr
  5. Save. Le secret est disponible dans l'onglet Credentials.

PKCE (Proof Key for Code Exchange, prononcé « pixy ») empêche l'interception du code d'autorisation. L'application génère un secret aléatoire à chaque connexion, en envoie l'empreinte à Keycloak, puis prouve qu'elle détient l'original au moment d'échanger le code contre un token. Sans le secret original, un code volé est inutilisable.

PKCE se règle par client, dans l'onglet Advanced :

  1. Ouvrez le client webapp-demo > onglet Advanced.

  2. Section Advanced settings > Proof Key for Code Exchange Code Challenge Method : choisissez S256.

  3. Save.

Votre bibliothèque OIDC (côté application) a besoin de connaître les URLs de Keycloak : où rediriger l'utilisateur, où échanger le code, où valider les tokens. Toutes ces adresses sont publiées à un seul endroit, le document de découverte :

Fenêtre de terminal
curl -s http://localhost:8080/realms/lab/.well-known/openid-configuration
{
"issuer": "http://localhost:8080/realms/lab",
"authorization_endpoint": "http://localhost:8080/realms/lab/protocol/openid-connect/auth",
"token_endpoint": "http://localhost:8080/realms/lab/protocol/openid-connect/token",
"userinfo_endpoint": "http://localhost:8080/realms/lab/protocol/openid-connect/userinfo",
"jwks_uri": "http://localhost:8080/realms/lab/protocol/openid-connect/certs",
"end_session_endpoint": "http://localhost:8080/realms/lab/protocol/openid-connect/logout"
}

La plupart des bibliothèques (Spring Security, oidc-client-ts, authlib en Python, oauth2-proxy) ne demandent que l'issuer et découvrent le reste automatiquement. Retenez ces trois adresses :

  • authorization_endpoint : où l'application envoie l'utilisateur pour se connecter.
  • token_endpoint : où l'application échange le code contre un token.
  • jwks_uri : les clés publiques pour vérifier la signature des tokens sans appeler Keycloak à chaque requête.

Étape 5 - Sécuriser un service sans utilisateur (Client Credentials)

Section intitulée « Étape 5 - Sécuriser un service sans utilisateur (Client Credentials) »

Tous les besoins ne concernent pas un humain. Un job planifié ou un backend qui appelle une autre API n'a pas d'utilisateur à connecter : il s'authentifie lui-même. C'est le flow Client Credentials, réservé aux clients confidentiels avec service account.

  1. Créez un client dédié au service, sans standard flow, avec service account activé :

    Fenêtre de terminal
    kcadm.sh create clients -r lab \
    -s clientId=backend-api \
    -s enabled=true \
    -s publicClient=false \
    -s serviceAccountsEnabled=true \
    -s standardFlowEnabled=false
  2. Récupérez le secret généré :

    Fenêtre de terminal
    cid=$(kcadm.sh get clients -r lab -q clientId=backend-api --fields id --format csv --noquotes)
    kcadm.sh get clients/$cid/client-secret -r lab
  3. Demandez un token avec le secret. Aucune interaction utilisateur, un simple appel serveur-à-serveur :

    Fenêtre de terminal
    curl -s -X POST http://localhost:8080/realms/lab/protocol/openid-connect/token \
    -d grant_type=client_credentials \
    -d client_id=backend-api \
    -d client_secret='LE_SECRET_DU_CLIENT'

Le token reçu est un JWT (JSON Web Token). Décodé, son contenu confirme le client émetteur et sa durée de vie courte :

{
"iss": "http://localhost:8080/realms/lab",
"typ": "Bearer",
"azp": "backend-api",
"preferred_username": "service-account-backend-api",
"scope": "profile email"
}

La durée de vie vaut ici 300 secondes (exp - iat). C'est volontaire : un token court limite la fenêtre d'exploitation s'il fuite. Le service redemande simplement un token quand l'ancien expire. Ne cherchez pas à allonger cette durée pour éviter les renouvellements ; c'est un mauvais compromis de sécurité.

Recevoir un token ne suffit pas : votre API doit le vérifier avant de faire confiance à son contenu. Deux approches, selon votre besoin.

La première, hors ligne, vérifie la signature du JWT avec les clés publiques de Keycloak (jwks_uri). C'est la méthode à privilégier pour une API : rapide, sans appel réseau à chaque requête. Keycloak expose ses clés ainsi :

Fenêtre de terminal
curl -s http://localhost:8080/realms/lab/protocol/openid-connect/certs

Le realm publie deux clés : une pour signer les tokens (RS256) et une pour le chiffrement (RSA-OAEP). Votre bibliothèque sélectionne la bonne via l'en-tête du JWT. La signature validée, vous savez que le token vient bien de Keycloak et n'a pas été modifié.

La seconde, en ligne, interroge l'endpoint userinfo avec le token. Utile pour récupérer les informations à jour de l'utilisateur :

Fenêtre de terminal
curl -s http://localhost:8080/realms/lab/protocol/openid-connect/userinfo \
-H "Authorization: Bearer LE_TOKEN"
{
"sub": "03816d65-6748-4977-8457-b3eb400a6fe4",
"email_verified": true,
"name": "Alice Martin",
"preferred_username": "alice",
"given_name": "Alice",
"family_name": "Martin",
"email": "alice@exemple.fr"
}

Quelques règles non négociables pour une intégration OIDC saine :

  • PKCE S256 obligatoire pour tout flow d'autorisation, public comme confidentiel. Jamais plain.
  • Redirect URIs exactes, jamais de joker large ni de localhost:* en production.
  • Client confidentiel dès que possible : si l'application a un backend, elle garde un secret, donc elle est confidentielle.
  • Secret client traité comme un mot de passe : jamais dans le dépôt Git, jamais dans le code front. Variable d'environnement ou coffre-fort. Voir HashiCorp Vault.
  • Durée de vie des tokens courte (5 minutes par défaut) et renouvellement par refresh token. Ne l'allongez pas par confort.
  • HTTPS partout : un token qui transite en clair est un token volé. En production, Keycloak et vos applications sont derrière TLS.
  • Validation systématique de la signature, de l'issuer et de l'audience côté API.
SymptômeCause probableSolution
Invalid parameter: redirect_uriL'URL de rappel de l'app n'est pas dans les Valid redirect URIsAjouter l'URL exacte dans la config du client
Erreur CORS dans la console du navigateurWeb origins non renseigné pour une SPAAjouter l'origine (https://app.exemple.fr) dans Web origins
invalid_client au token endpointMauvais secret, ou client public appelé avec un secretVérifier le secret dans Credentials ; un client public n'en a pas
unauthorized_client avec client_credentialsService account non activéActiver Service accounts roles sur le client
Account is not fully set upL'utilisateur a une action requise en attente (profil incomplet)Renseigner firstName/lastName et lever les required actions du compte
Token accepté mais rôles absentsLe scope ou le mapper ne remonte pas les rôlesVérifier les Client scopes et les mappers du client
  • Un client Keycloak représente une application ; le premier choix est public ou confidentiel selon la capacité à garder un secret.
  • Authorization Code + PKCE S256 est le standard pour toute application interactive. Le flow implicite est abandonné.
  • Les redirect URIs exactes protègent contre le vol de token ; les web origins débloquent les appels CORS des SPA.
  • Le document .well-known/openid-configuration donne tous les endpoints ; la plupart des bibliothèques n'ont besoin que de l'issuer.
  • Le flow client_credentials authentifie un service sans utilisateur, via un client confidentiel avec service account.
  • Une API valide toujours la signature (via jwks_uri), l'issuer et l'audience avant de faire confiance à un token.

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