Tous les articles
25 août 20268 min de lecture

Authentification et autorisation : la différence qui change tout

Comprendre la différence entre authentification et autorisation, pourquoi un utilisateur connecté peut recevoir un 403, et où l’API doit décider.

  • IAM
  • Identity Security
  • OIDC
  • RBAC

Le problème

Un utilisateur se connecte correctement à une application. Son identité est reconnue, sa session est créée, son nom apparaît dans l’interface.

Puis il ouvre /admin.

403 Forbidden.

Quand on vient du développement web, la réaction naturelle est parfois : « Pourtant, il est connecté. »

Justement.

La connexion répond à une première question : qui fait la demande ?

L’accès à /admin en pose une deuxième : cette identité a-t-elle le droit d’effectuer cette action sur cette ressource ?

C’est toute la différence entre authentification et autorisation.

L’authentification établit une identité. L’autorisation décide ce que cette identité peut faire.

La distinction semble élémentaire jusqu’au moment où une application possède plusieurs rôles, une API séparée, du SSO, des droits temporaires ou un processus de révocation. À ce stade, considérer qu’un utilisateur authentifié est automatiquement autorisé devient une vraie faille de conception.

Le modèle mental

Je garde deux décisions séparées en tête :

Requête
   ↓
Authentification
« Qui fait la demande ? »
   ↓
Identité établie
   ↓
Autorisation
« Cette identité peut-elle faire cette action
sur cette ressource ? »
   ↓
Ressource

L’authentification produit un contexte d’identité : un utilisateur, une application ou un service dont l’identité a été vérifiée.

L’autorisation utilise ensuite ce contexte pour prendre une décision d’accès.

Authentification Autorisation
Question Qui es-tu ? Que peux-tu faire ici ?
Résultat Identité établie ou non Accès accordé ou refusé
Exemples mot de passe, passkey, certificat, OIDC rôle, groupe, scope, permission, attribut
Échec typique côté API 401 403

Un même utilisateur peut donc être parfaitement authentifié et ne posséder aucun droit sur une ressource particulière.

Ce n’est pas une anomalie. C’est précisément ce qu’un système de contrôle d’accès doit être capable d’exprimer.

Comment ça fonctionne

Dans une application moderne, l’authentification est souvent déléguée à un Identity Provider comme Keycloak ou Microsoft Entra ID.

Avec OpenID Connect, l’application peut s’appuyer sur une authentification réalisée par un fournisseur d’identité au lieu de gérer elle-même tout le mécanisme de connexion. OIDC est une couche d’identité construite au-dessus d’OAuth 2.0.

La distinction compte : OAuth 2.0 fournit un framework d’autorisation ; OpenID Connect ajoute l’authentification et l’identité de l’utilisateur.

Dans une architecture avec une API protégée, la chaîne peut ressembler à ceci :

Utilisateur
   ↓
Identity Provider
   ↓
Access token
   ↓
API
   ↓
Validation du token
   ↓
Identité connue
   ↓
Contrôle des droits
   ↓
Ressource

L’API commence par vérifier si elle peut faire confiance au token présenté : sa signature, son émetteur, son audience, sa durée de validité et les claims dont elle a besoin.

Cette étape permet d’établir un contexte fiable.

Mais un token valide ne répond pas encore forcément à la question métier : l’appelant a-t-il le droit de faire cette opération précise ?

L’API doit encore appliquer sa politique d’autorisation : rôle requis, scope, permission, appartenance à un groupe, ownership d’une ressource, grant actif en base, ou autre règle métier.

Le token transporte donc souvent de l’information utile à l’autorisation. Il ne remplace pas la décision d’autorisation.

Cette séparation se retrouve aussi dans les statuts HTTP. Le nom 401 Unauthorized est historiquement assez mal choisi, mais la sémantique HTTP est claire : des credentials absents ou invalides conduisent normalement à 401, tandis que des credentials valides mais insuffisants peuvent conduire à 403 Forbidden. La distinction est définie dans la RFC 9110.

Exemple concret

Prenons une application interne de gestion financière.

Alice et Bob utilisent le même SSO. Les deux passent par le même Identity Provider et réussissent leur connexion.

Ils sont donc tous les deux authentifiés.

Alice possède cependant le rôle nécessaire pour approuver une dépense. Bob travaille au support.

Quand Bob consulte son profil, l’accès passe.

Quand Bob tente d’approuver une dépense de 25 000 €, l’application refuse.

Bob est bien Bob
→ authentification réussie

Bob peut approuver cette dépense
→ autorisation refusée

Changer son mot de passe, refaire un MFA ou relancer sa session ne modifiera rien.

Le problème n’est pas son identité.

Le problème est le droit associé à cette identité pour cette action.

Ce modèle devient encore plus important avec des applications où les droits évoluent dans le temps. Une personne peut garder la même identité pendant des années alors que ses rôles, groupes, responsabilités et accès changent plusieurs fois.

Dans mon lab

J’ai voulu rendre cette séparation visible dans mon Identity Security Lab au lieu de la laisser dans un schéma.

L’architecture utilise Keycloak comme Identity Provider, une application Next.js et une API Express indépendante qui agit comme resource server.

Navigateur
   ↓
Next.js
   ↓ OIDC
Keycloak
   ↓
Access token
   ↓
API Express
   ↓
PostgreSQL

Quand une requête arrive sur l’API, le middleware authenticate commence par examiner le Bearer token.

Avec la bibliothèque jose, l’API vérifie notamment :

  • la signature RS256 à partir du JWKS de Keycloak ;
  • l’issuer attendu ;
  • l’audience de l’API ;
  • l’expiration du token ;
  • la présence du claim sub.

Si cette étape échoue, aucune identité fiable n’est établie et l’API répond 401.

Mais la chaîne ne s’arrête volontairement pas à « le JWT est valide ».

Après l’authentification, l’application rattache l’identité Keycloak à un utilisateur local puis recalcule ses droits effectifs :

droits effectifs
=
rôles du token Keycloak
+
grants ACTIVE en base

Ce calcul est effectué côté serveur à chaque requête.

Le middleware requireRole intervient ensuite. Il compare les rôles effectifs de l’utilisateur avec ceux exigés par la route.

Par exemple, l’endpoint :

GET /api/admin/summary

exige explicitement le rôle admin.

Avec le token valide d’un utilisateur qui ne possède que le rôle user, l’authentification réussit mais l’autorisation échoue. L’API renvoie alors 403.

C’est exactement le comportement que je voulais démontrer :

Token valide
≠
accès autorisé

Le frontend masque également les liens correspondant aux zones non autorisées, mais je ne considère pas cela comme une mesure de sécurité. Un bouton caché peut être contourné avec les DevTools ou un appel curl. Le contrôle qui fait autorité reste celui de l’API.

J’ai retrouvé la même séparation dans un lab Microsoft Entra et Azure RBAC. Un utilisateur B2B pouvait exister comme identité Guest dans le tenant sans disposer d’aucun accès au resource group. Son ajout à un groupe de sécurité lui accordait ensuite Reader, puis le retrait du groupe supprimait cet accès sans supprimer son identité.

Autrement dit :

identité présente
≠
entitlement présent

Deux implémentations différentes, même principe IAM.

Ce qui peut mal tourner

La première erreur classique consiste à protéger une action sensible avec un simple test du type « l’utilisateur possède une session ».

Cela prouve seulement qu’une identité a été établie.

Si une route d’administration s’arrête à ce contrôle, n’importe quel utilisateur authentifié peut potentiellement l’appeler.

Deuxième erreur : faire confiance au frontend.

Masquer /admin dans la navigation améliore l’expérience utilisateur. Cela ne protège pas GET /api/admin/summary. Si l’API n’effectue pas son propre contrôle, l’utilisateur peut appeler directement l’endpoint.

Troisième erreur : confondre un token valide avec une autorisation valide.

Un access token peut être parfaitement signé, provenir du bon issuer et être destiné à la bonne API tout en ne donnant pas le droit d’exécuter l’action demandée.

Le diagnostic devient beaucoup plus simple quand on sépare les étapes.

Imaginons qu’un utilisateur se connecte normalement puis reçoive 403 sur une route admin. Refaire le login n’est pas le premier réflexe utile : son identité est déjà connue.

Il faut plutôt vérifier :

1. Le token a-t-il été accepté ?
2. Quelle identité l’API a-t-elle établie ?
3. Quels droits effectifs l’API calcule-t-elle ?
4. Quel rôle ou quelle permission la route exige-t-elle ?

Dans mon lab, si le JWT passe la validation mais que admin n’apparaît ni dans les rôles Keycloak ni dans les grants actifs, le 403 est normal. Le problème est dans l’autorisation, pas dans le SSO.

À l’inverse, si la signature est invalide, que l’audience est mauvaise ou que le token a expiré, il faut investiguer l’authentification et la validation du token.

Les deux symptômes peuvent se ressembler côté utilisateur : « je n’ai pas accès ». Techniquement, ils racontent deux histoires différentes.

À retenir

  • Authentifier, c’est établir qui fait la demande.
  • Autoriser, c’est décider si cette identité peut effectuer une action sur une ressource.
  • Un utilisateur connecté n’est pas automatiquement autorisé à tout faire.
  • Un token valide peut transporter des informations d’autorisation sans constituer à lui seul la décision finale.
  • Le frontend peut refléter les permissions, mais le contrôle qui fait autorité doit être appliqué côté ressource protégée.
  • 401 et 403 orientent vers deux diagnostics différents : identité non établie d’un côté, droits insuffisants de l’autre.

Pour un développeur, cette séparation change finalement assez peu la page de login.

Elle change surtout toute l’architecture qui vient après.

La suite logique consiste donc à regarder comment cette identité arrive réellement jusqu’à l’application : c’est le rôle du flux SSO avec OpenID Connect.

Le blog

Une question, un désaccord, une précision à apporter ?

Écrivez-moi