Aller au contenu principal

RBAC en pratique : Mettre en œuvre une autorisation sécurisée pour votre application

Vous avez du mal à mettre en place un système d'autorisation sécurisé et évolutif pour votre application ? Le contrôle d’accès basé sur les rôles (RBAC) est la norme de l'industrie pour la gestion des permissions utilisateur, mais sa mise en œuvre correcte peut être complexe. Ce tutoriel va vous montrer comment construire un système RBAC robuste à l’aide d’un exemple concret de système de gestion de contenu (CMS).

En suivant ce guide, vous apprendrez :

  • ✨ Comment concevoir et mettre en œuvre des permissions fines pour un contrôle précis
  • 🔒 Les meilleures pratiques pour organiser les permissions en rôles significatifs
  • 👤 Des techniques pour gérer efficacement la propriété des ressources
  • 🚀 Des moyens de rendre votre système d’autorisation évolutif et maintenable
  • 💡 Une mise en œuvre pratique à l’aide d’un exemple réel de CMS

Le code source complet de ce tutoriel est disponible sur GitHub.

Comprendre les fondamentaux du RBAC

Le contrôle d’accès basé sur les rôles (RBAC) ne consiste pas seulement à attribuer des permissions aux utilisateurs. Il s’agit de créer une approche structurée de l’autorisation qui équilibre sécurité et maintenabilité.

Vous pouvez en apprendre davantage sur Qu’est-ce que le RBAC dans l’Auth Wiki.

Voici les principes clés que nous suivrons dans notre mise en œuvre :

Conception de permissions fines

Des permissions fines vous donnent un contrôle précis sur ce que les utilisateurs peuvent faire dans votre système. Au lieu de niveaux d’accès larges comme "admin" ou "utilisateur", nous définissons des actions spécifiques que les utilisateurs peuvent effectuer sur les ressources. Par exemple :

  • read:articles - Voir n’importe quel article du système
  • create:articles - Créer de nouveaux articles
  • update:articles - Modifier des articles existants
  • publish:articles - Changer le statut de publication des articles

Propriété des ressources et contrôle d’accès

La propriété des ressources est un concept fondamental dans la conception de l’autorisation de notre CMS. Alors que le RBAC définit quelles actions différents rôles peuvent effectuer, la propriété ajoute une dimension personnelle au contrôle d’accès :

  • Les auteurs ont automatiquement accès aux articles qu’ils ont créés
  • Ce modèle naturel de propriété signifie que les auteurs peuvent toujours voir et éditer leur propre contenu
  • Le système vérifie à la fois les permissions de rôle OU la propriété lors du traitement des opérations sur les articles
  • Par exemple, même sans la permission update:articles, un auteur peut toujours modifier ses propres articles
  • Cette conception réduit le besoin de permissions supplémentaires tout en maintenant la sécurité

Cette approche à double couche (rôles + propriété) crée un système plus intuitif et sécurisé. Les éditeurs et les administrateurs peuvent toujours gérer tout le contenu via leurs permissions de rôle, tandis que les auteurs gardent le contrôle sur leur propre travail.

Concevoir des APIs sécurisées

Commençons par concevoir la fonctionnalité principale de notre CMS via ses points de terminaison API :

GET    /api/articles         # Lister tous les articles
GET /api/articles/:id # Obtenir un article spécifique
POST /api/articles # Créer un nouvel article
PATCH /api/articles/:id # Mettre à jour un article
DELETE /api/articles/:id # Supprimer un article
PATCH /api/articles/:id/published # Changer le statut de publication

Mettre en œuvre le contrôle d’accès pour votre API

Pour chaque point de terminaison, nous devons considérer deux aspects du contrôle d’accès :

  1. Propriété de la ressource - L’utilisateur possède-t-il cette ressource ?
  2. Permissions basées sur les rôles - Le rôle de l’utilisateur autorise-t-il cette opération ?

Voici comment nous allons gérer l’accès pour chaque point de terminaison :

EndpointLogique de contrôle d’accès
GET /api/articles- Toute personne avec la permission list:articles, OU les auteurs voient leurs propres articles
GET /api/articles/:id- Toute personne avec la permission read:articles, OU l’auteur de l’article
POST /api/articles- Toute personne avec la permission create:articles
PATCH /api/articles/:id- Toute personne avec la permission update:articles, OU l’auteur de l’article
DELETE /api/articles/:id- Toute personne avec la permission delete:articles, OU l’auteur de l’article
PATCH /api/articles/:id/published- Seuls les utilisateurs avec la permission publish:articles

Créer un système de permissions évolutif

En fonction de nos besoins d’accès à l’API, nous pouvons définir ces permissions :

PermissionDescription
list:articlesVoir la liste de tous les articles du système
read:articlesLire le contenu complet de n’importe quel article
create:articlesCréer de nouveaux articles
update:articlesModifier n’importe quel article
delete:articlesSupprimer n’importe quel article
publish:articlesChanger le statut de publication

Notez que ces permissions ne sont nécessaires que pour accéder à des ressources que vous ne possédez pas. Les propriétaires d’articles peuvent automatiquement :

  • Voir leurs propres articles (pas besoin de read:articles)
  • Modifier leurs propres articles (pas besoin de update:articles)
  • Supprimer leurs propres articles (pas besoin de delete:articles)

Construire des rôles efficaces

Maintenant que nous avons défini notre API et nos permissions, nous pouvons créer des rôles qui regroupent logiquement ces permissions :

Permission/Rôle👑 Admin📝 Publisher✍️ Author
DescriptionAccès complet au système pour la gestion du contenuPeut voir tous les articles et gérer la publicationPeut créer de nouveaux articles dans le système
list:articles
read:articles
create:articles
update:articles
delete:articles
publish:articles

Remarque : Les auteurs ont automatiquement les permissions de lecture / modification / suppression sur leurs propres articles, indépendamment des permissions de rôle.

Chaque rôle est conçu avec des responsabilités spécifiques en tête :

  • Admin : A le contrôle total du CMS, y compris toutes les opérations sur les articles
  • Publisher : Se concentre sur la relecture du contenu et la gestion de la publication
  • Author : Spécialisé dans la création de contenu

Cette structure de rôles crée une séparation claire des responsabilités :

  • Les auteurs se concentrent sur la création de contenu
  • Les éditeurs gèrent la qualité et la visibilité du contenu
  • Les administrateurs maintiennent le contrôle global du système

Configurer le RBAC dans Logto

Avant de commencer, vous devez créer un compte sur Logto Cloud, ou vous pouvez également utiliser une instance Logto auto-hébergée en utilisant la version Logto OSS.

Mais pour ce tutoriel, nous utiliserons Logto Cloud pour plus de simplicité.

Configurer votre application

  1. Allez dans "Applications" dans la Console Logto pour créer une nouvelle application React
    • Nom de l’application : Content Management System
    • Type d’application : Application Web traditionnelle
    • URIs de redirection : http://localhost:5173/callback

Application CMS React

Configurer les ressources API et les permissions

  1. Allez dans "Ressources API" dans la Console Logto pour créer une nouvelle ressource API
    • Nom de l’API : CMS API
    • Identifiant de l’API : https://api.cms.com
    • Ajoutez les permissions à la ressource API
      • list:articles
      • read:articles
      • create:articles
      • update:articles
      • publish:articles
      • delete:articles

Détails de la ressource API CMS

Créer des rôles

Allez dans Rôles dans la Console Logto pour créer les rôles suivants pour le CMS

  • Admin
    • avec toutes les permissions
  • Publisher
    • avec read:articles, list:articles, publish:articles
  • Author
    • avec create:articles

Rôle Admin

Rôle Publisher

Rôle Author

Attribuer des rôles aux utilisateurs

Allez dans la section "Gestion des utilisateurs" dans la Console Logto pour créer des utilisateurs.

Dans l’onglet "Rôles" des détails de l’utilisateur, vous pouvez attribuer des rôles à l’utilisateur.

Dans notre exemple, nous créons 3 utilisateurs avec les rôles suivants :

  • Alex : Admin
  • Bob : Publisher
  • Charlie : Author

Gestion des utilisateurs

Détails utilisateur - Alex

remarque:

À des fins de démonstration, nous créons ces ressources et configurations via la Console Logto. Dans des projets réels, vous pouvez créer ces ressources et configurations de manière programmatique en utilisant la Management API fournie par Logto.

Intégrer votre frontend avec le RBAC Logto

Maintenant que nous avons configuré le RBAC dans Logto, nous pouvons commencer à l’intégrer dans notre frontend.

Commencez par suivre les Logto Quick Starts pour intégrer Logto dans votre application.

Dans notre exemple, nous utilisons React pour la démonstration.

Après avoir configuré Logto dans votre application, nous devons ajouter les configurations RBAC pour que Logto fonctionne.

// frontend/src/App.tsx

const logtoConfig: LogtoConfig = {
appId: LOGTO_APP_ID, // L’ID de l’application que vous avez créé dans la Console Logto
endpoint: LOGTO_ENDPOINT, // L’endpoint que vous avez créé dans la Console Logto
resources: [API_RESOURCE], // L’identifiant de la ressource API que vous avez créé dans la Console Logto, par exemple https://api.cms.com
// Toutes les portées que vous souhaitez demander à la ressource API dans le frontend
scopes: [
'list:articles',
'create:articles',
'read:articles',
'update:articles',
'delete:articles',
'publish:articles',
],
};

N’oubliez pas de vous déconnecter et de vous reconnecter pour que ce changement prenne effet si vous êtes déjà connecté.

Lorsque l’utilisateur se connecte avec Logto et demande un jeton d’accès pour les ressources API spécifiées ci-dessus, Logto ajoutera les portées (permissions) liées au rôle de l’utilisateur dans le jeton d’accès.

Vous pouvez utiliser getAccessTokenClaims du hook useLogto pour obtenir les portées à partir du jeton d’accès.

// frontend/src/hooks/use-user-data.ts

import { useLogto } from '@logto/react';
import { API_RESOURCE } from '../config';
import { useState, useEffect } from 'react';

export const useUserData = () => {
const { getAccessTokenClaims } = useLogto();
const [userScopes, setUserScopes] = useState<string[]>([]);
const [userId, setUserId] = useState<string>();

useEffect(() => {
const fetchScopes = async () => {
const token = await getAccessTokenClaims(API_RESOURCE);
setUserScopes(token?.scope?.split(' ') ?? []);
setUserId(token?.sub);
};

fetchScopes();
}, [getAccessTokenClaims]);

return { userId, userScopes };
};

Et vous pouvez utiliser userScopes pour vérifier si l’utilisateur a la permission d’accéder à la ressource.

// frontend/src/pages/Dashboard.tsx

const Dashboard = () => {
const { userId, userScopes } = useUserData();
// ...

return (
<div>
{/* ... */}
{(userScopes.includes('delete:articles') || article.ownerId === userId) && (
<button
onClick={() => handleDelete(article.id)}
className="text-red-600 hover:text-red-900"
>
Delete
</button>
)}
</div>
);
};

Intégrer votre backend avec le RBAC Logto

Il est maintenant temps d’intégrer le RBAC Logto dans votre backend.

Middleware d’autorisation backend

Tout d’abord, nous devons ajouter un middleware dans le backend pour vérifier les permissions utilisateur, s’assurer que l’utilisateur est connecté et déterminer s’il dispose des permissions nécessaires pour accéder à certaines APIs.

// backend/src/middleware/auth.js

const { createRemoteJWKSet, jwtVerify } = require('jose');

const getTokenFromHeader = (headers) => {
const { authorization } = headers;
const bearerTokenIdentifier = 'Bearer';

if (!authorization) {
throw new Error('Authorization header missing');
}

if (!authorization.startsWith(bearerTokenIdentifier)) {
throw new Error('Authorization token type not supported');
}

return authorization.slice(bearerTokenIdentifier.length + 1);
};

const hasScopes = (tokenScopes, requiredScopes) => {
if (!requiredScopes || requiredScopes.length === 0) {
return true;
}
const scopeSet = new Set(tokenScopes);
return requiredScopes.every((scope) => scopeSet.has(scope));
};

const verifyJwt = async (token) => {
const JWKS = createRemoteJWKSet(new URL(process.env.LOGTO_JWKS_URL));

const { payload } = await jwtVerify(token, JWKS, {
issuer: process.env.LOGTO_ISSUER,
audience: process.env.LOGTO_API_RESOURCE,
});

return payload;
};

const requireAuth = (requiredScopes = []) => {
return async (req, res, next) => {
try {
// Extraire le jeton
const token = getTokenFromHeader(req.headers);

// Vérifier le jeton
const payload = await verifyJwt(token);

// Ajouter les infos utilisateur à la requête
req.user = {
id: payload.sub,
scopes: payload.scope?.split(' ') || [],
};

// Vérifier les portées requises
if (!hasScopes(req.user.scopes, requiredScopes)) {
throw new Error('Insufficient permissions');
}

next();
} catch (error) {
res.status(401).json({ error: 'Unauthorized' });
}
};
};

module.exports = {
requireAuth,
hasScopes,
};

Comme vous pouvez le voir, dans ce middleware, nous vérifions si la requête frontend contient un jeton d’accès valide et si l’audience du jeton d’accès correspond à la ressource API que nous avons créée dans la Console Logto.

La raison de la vérification de la ressource API est que notre ressource API représente en réalité les ressources de notre backend CMS, et toutes nos permissions CMS sont associées à cette ressource API.

Puisque cette ressource API représente les ressources CMS dans Logto, dans notre code frontend, nous incluons le jeton d’accès correspondant lors des requêtes API vers le backend :

// frontend/src/hooks/use-api.ts
export const useApi = () => {
const { getAccessToken } = useLogto();

return useMemo(
() =>
async (endpoint: string, options: RequestInit = {}) => {
try {
// Obtenir le jeton d’accès pour la ressource API
const token = await getAccessToken(API_RESOURCE);

if (!token) {
throw new ApiRequestError('Failed to get access token');
}

const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
// Ajouter le jeton d’accès dans les en-têtes de la requête
Authorization: `Bearer ${token}`,
...options.headers,
},
});

// ... gestion de la réponse

return await response.json();
} catch (error) {
// ... gestion des erreurs
}
},
[getAccessToken]
);
};

Nous pouvons maintenant utiliser le middleware requireAuth pour protéger nos points de terminaison API.

Protéger les points de terminaison API

Pour les APIs qui ne doivent être accessibles qu’aux utilisateurs disposant de permissions spécifiques, nous pouvons ajouter des restrictions directement dans le middleware. Par exemple, l’API de création d’article ne doit être accessible qu’aux utilisateurs ayant la permission create:articles :

// backend/src/routes/articles.js

const { requireAuth } = require('../middleware/auth');

router.post('/articles', requireAuth(['create:articles']), async (req, res) => {
// ...
});

Pour les APIs qui doivent vérifier à la fois les permissions et la propriété de la ressource, nous pouvons utiliser la fonction hasScopes. Par exemple, dans l’API de liste des articles, les utilisateurs ayant la portée list:articles peuvent accéder à tous les articles, tandis que les auteurs peuvent accéder à leurs propres articles créés :

// backend/src/routes/articles.js

const { requireAuth, hasScopes } = require('../middleware/auth');

router.get('/articles', requireAuth(), async (req, res) => {
try {
// Si l’utilisateur a la portée list:articles, retourner tous les articles
if (hasScopes(req.user.scopes, ['list:articles'])) {
const articles = await articleDB.list();
return res.json(articles);
}

// Sinon, retourner uniquement les articles de l’utilisateur
const articles = await articleDB.listByOwner(req.user.id);
res.json(articles);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch articles' });
}
});

À ce stade, nous avons terminé la mise en œuvre du RBAC. Vous pouvez consulter le code source complet pour voir l’implémentation complète.

Tester la mise en œuvre du RBAC dans le CMS

Testons maintenant notre mise en œuvre du RBAC dans le CMS à l’aide des trois utilisateurs que nous venons de créer.

remarque:

Si vous constatez que vous ne pouvez pas vous connecter avec les identifiants des utilisateurs créés dans "Gestion des utilisateurs", vous devrez d’abord activer la méthode de connexion appropriée. Allez dans "Expérience de connexion" dans la Console Logto et activez votre méthode d’authentification préférée (par exemple Email + Mot de passe ou Nom d’utilisateur + Mot de passe).

Commençons par nous connecter en tant qu’Alex et Charles respectivement et créons quelques articles.

Puisqu’Alex a le rôle Admin, il peut créer, supprimer, mettre à jour, publier et voir tous les articles.

Tableau de bord CMS - Alex

Charles, ayant le rôle Author, ne peut créer que ses propres articles et ne peut voir, mettre à jour et supprimer que les articles qu’il possède.

Tableau de bord CMS - Charles - Liste des articles

Bob, avec le rôle Publisher, peut voir et publier tous les articles mais ne peut pas les créer, les mettre à jour ou les supprimer.

Tableau de bord CMS - Bob

Conclusion

Félicitations ! Vous avez appris à mettre en œuvre un système RBAC robuste dans votre application.

Pour des scénarios plus complexes, comme la création d’applications multi-locataires, Logto offre un support complet des organisations. Consultez notre guide Construire une application SaaS multi-locataires : Guide complet de la conception à la mise en œuvre pour en savoir plus sur la mise en œuvre du contrôle d’accès à l’échelle de l’organisation.

Bon code ! 🚀