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èmecreate:articles
- Créer de nouveaux articlesupdate:articles
- Modifier des articles existantspublish: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 :
- Propriété de la ressource - L’utilisateur possède-t-il cette ressource ?
- 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 :
Endpoint | Logique 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 :
Permission | Description |
---|---|
list:articles | Voir la liste de tous les articles du système |
read:articles | Lire le contenu complet de n’importe quel article |
create:articles | Créer de nouveaux articles |
update:articles | Modifier n’importe quel article |
delete:articles | Supprimer n’importe quel article |
publish:articles | Changer 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 |
---|---|---|---|
Description | Accès complet au système pour la gestion du contenu | Peut voir tous les articles et gérer la publication | Peut 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
- 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
Configurer les ressources API et les permissions
- 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
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
- avec
- Author
- avec
create:articles
- avec
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
À 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.
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.
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.
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.
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 ! 🚀