Accueil / Articles

Authentification versus autorisation : où chacune trouve sa place dans votre code

L’authentification prouve qui vous êtes, l’autorisation décide de ce que vous pouvez faire. Où chacune se situe dans le middleware, 401 contre 403, ainsi que les erreurs liées à leur confusion.

Éditeur : REACTAPP.TOP6 min de lecture1335 mots

Sources: Authentication vs Authorization: The Distinction Most Developers Get Wrong

Deux mots, presque interchangeables à première vue, mais chacun régissant une partie complètement différente de la logique de votre système.

Si vous avez déjà renvoyé un code 401 dans une situation qui exigeait en réalité un 403, ou si vous avez mis en place un flux de connexion tout en déclarant que l’authentification était terminée sans jamais toucher à la logique des permissions, vous avez déjà rencontré les problèmes liés au mélange de ces deux concepts. Débrouillons-les une bonne fois pour toutes.

Authentification : Vérification de l’identité

L’authentification est l’étape où le système confirme qu’un utilisateur est bien celui qu’il prétend être. Elle répond à une seule question : est-ce que cette identité est valide ?

C’est un événement unique, qui a généralement lieu lors de la connexion, et il produit une sorte de clé d’authentification — une session, un jeton, une cookie — qui permet au reste de l’application de faire confiance à l’utilisateur sans avoir à valider à nouveau le mot de passe à chaque requête.

Les méthodes typiques pour mettre en œuvre l’authentification incluent :

  • Authentification basée sur le mot de passe — les identifiants soumis sont comparés à une valeur hachée stockée
  • Authentification basée sur des jetons (JWT) — un jeton signé est généré après une connexion réussie, puis vérifié à chaque appel ultérieur
  • OAuth 2.0 / OpenID Connect — la vérification de l’identité est confiée à un fournisseur externe tel que Google ou GitHub
  • Clés API — une chaîne secrète fixe qui identifie un client ou un service particulier
  • Authentification multifacteur (MFA) — une étape de vérification supplémentaire (un code unique, une application d’authentification) ajoutée en plus du mot de passe
  • Voici à quoi ressemble un échange d’authentification standard :

    POST /api/login
    Content-Type: application/json
    
    {
      "email": "user@example.com",
      "password": "hashed_and_verified_serverside"
    }
    
    Response: 200 OK
    {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
    

    Le serveur valide les identifiants soumis et, une fois qu’ils sont vérifiés, renvoie un jeton. À partir de ce moment-là, ce jeton sert de preuve d’identité pour l’utilisateur à chaque demande :

    GET /api/profile
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    

    Le middleware chargé de valider ce jeton ressemble généralement à ceci :

    function authenticate(req, res, next) {
      const token = req.headers.authorization?.split(' ')[1];
      if (!token) return res.status(401).json({ error: 'No token provided' });
    
      jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
        if (err) return res.status(401).json({ error: 'Invalid or expired token' });
        req.user = decoded;
        next();
      });
    }
    

    Faites attention à ce que ce middleware omet délibérément. À aucun moment il ne décide de ce que l’utilisateur est autorisé à faire. Il se contente de confirmer que le jeton est légitime et d’attacher la charge utile décodée à l’objet de requête. Rien d’autre ne relève des responsabilités de l’authentification.

    Autorisation : Vérification des permissions

    L’autorisation ne prend effet qu’après que l’authentification ait déjà réussi. Elle répond à une question complètement distincte : cette identité vérifiée a-t-elle le droit d’effectuer cette action en particulier ?

    Quelques approches courantes pour structurer l’autorisation :

    • Contrôle d’accès basé sur les rôles (RBAC) — les permissions sont associées à des rôles tels que admin, editor ou viewer
  • Contrôle d’accès basé sur les attributs (ABAC) — les décisions d’accès sont calculées à partir d’attributs tels que le département, la région, le propriétaire du ressource ou même l’heure de la journée
  • Listes de contrôle d’accès (ACL) — les permissions sont définies explicitement pour chaque utilisateur et chaque ressource
  • Champs d’application (OAuth) — le jeton lui-même encode les opérations spécifiques autorisées, comme read:messages ou write:files
  • Vérification typique de l’autorisation effectuée sur une requête qui a déjà passé l’authentification :

    function authorize(requiredRole) {
      return (req, res, next) => {
        if (req.user.role !== requiredRole) {
          return res.status(403).json({ error: 'Insufficient permissions' });
        }
        next();
      };
    }
    
    app.get('/api/admin/dashboard', authenticate, authorize('admin'), (req, res) => {
      res.json({ data: 'sensitive admin data' });
    });
    

    Ces deux fonctions de middleware distinctes gèrent des tâches différentes et s’exécutent dans un ordre fixe : authenticate détermine l’identité de l’utilisateur, tandis que authorize décide si cet utilisateur a le droit de continuer. Une requête peut passer le premier contrôle sans pour autant être rejetée par le second — et ce rejet n’a rien à voir avec l’identité.

    Les codes d’état qui reflètent réellement cette différence

    C’est ici que la distinction théorique se transforme en quelque chose que l’on peut vérifier littéralement dans le code.

    401 Unauthorized — le nom est trompeur, mais ce code indique en réalité un problème d’authentification. Il signifie que le serveur n’a reçu aucune identifiante, ou que celles qu’il a reçues (y compris un jeton) sont invalides ou expirées. À ce stade, le serveur ne sait pas avec certitude qui envoie la requête.

    403 Interdit — cet état indique un problème d’autorisation. Le serveur connaît déjà l’identité de l’utilisateur ; il refuse simplement à cette identité d’exécuter l’action demandée ou d’accéder au ressource requise.

    // Authentication failure — identity unknown or invalid
    res.status(401).json({ error: 'Invalid credentials' });
    
    // Authorization failure — identity known, permission denied
    res.status(403).json({ error: 'You do not have access to this resource' });
    

    Envoyer un 401 pour ce qui est en réalité un problème de permissions est une erreur fréquente. Cela pousse le client à se connecter à nouveau, alors que le véritable problème est que la connexion fonctionne parfaitement — le compte ne possède tout simplement pas le rôle requis. Cette confusion seule peut transformer ce qui devrait être une solution rapide en une longue session de débogage inutile du côté client, à la recherche d’un bug de connexion qui n’a jamais existé.

    Où cela se produit dans des architectures réelles

    Les charges utiles JWT mélangent fréquemment ces éléments sans que personne ne le veuille. Un jeton est conçu pour contenir des données d’identité — comme sub, email, iat et exp. Le fait qu’il doive également inclure des données de permission, telles que role ou scope, relève d’une décision de conception qui dépend du système. Intégrer directement les rôles dans le jeton est pratique et évite des recherches supplémentaires, mais cela a un coût réel : toute modification des permissions d’un utilisateur ne s’appliquera qu’une fois ce jeton réémis. Il s’agit d’un véritable compromis, et non d’une solution rapide sans inconvénients.

    {
      "sub": "user_12345",
      "email": "user@example.com",
      "role": "editor",
      "iat": 1710000000,
      "exp": 1710003600
    }
    

    Les systèmes basés sur des sessions côté serveur ont tendance à séparer davantage ces deux aspects. L’identifiant de session gère l’authentification, tandis que les permissions sont récupérées directement depuis une base de données à chaque demande. Cela évite le problème de désuétude mentionné précédemment, mais au prix d’une requête supplémentaire vers la base de données par demande.

    Dans les architectures à microservices, l’authentification est généralement centralisée — gérée une seule fois par un gateway ou un fournisseur d’identité tel qu’Auth0, Keycloak ou AWS Cognito, qui émet un jeton. En revanche, l’autorisation est habituellement laissée à chaque service individuel, qui lit les informations contenues dans ce jeton et applique ses propres règles. Il en résulte qu’un même événement d’authentification peut être interprété différemment par dix services distincts, chacun décidant de manière indépendante des actions autorisées pour cette identité.

    La règle qui empêche la plupart des bugs de sécurité

    L’authentification doit avoir lieu avant l’autorisation, et les deux ne doivent jamais être fusionnées en une seule vérification combinée.

    // Correct: sequential, separate concerns
    app.post('/api/posts/:id/delete', authenticate, authorize('editor'), deleteHandler);
    
    // Wrong: conflating identity with permission in one step
    app.post('/api/posts/:id/delete', (req, res) => {
      if (req.headers.authorization === 'valid-token-and-also-admin') {
        // fragile, unscalable, and impossible to audit correctly
      }
    });
    

    Un système qui authentifie de manière rigoureuse mais omet l’autorisation confirme parfaitement l’identité, puis permet à chaque identité confirmée de faire ce qu’elle veut — donc la compromission d’un seul compte à faibles privilèges revient en pratique à une compromission totale. Inversement, un système doté d’une autorisation finement ajustée mais reposant sur une authentification faible présente le problème inverse : des permissions soigneusement définies protègent une porte que n’importe qui peut franchir sans obstacle.

    Les deux couches doivent être solides, appliquées dans le bon ordre et traitées comme des éléments distincts. C’est toute la différence — et une fois que cela est correctement reflété dans vos codes d’état, votre chaîne de middleware et la manière dont vous concevez vos tokens, toute une catégorie d’erreurs de sécurité disparaît simplement.

    Lectures complémentaires