Protégez votre API Ruby on Rails avec le contrôle d’accès basé sur les rôles (RBAC) et la validation JWT
Ce guide vous aidera à implémenter l'autorisation pour sécuriser vos APIs Ruby on Rails en utilisant le contrôle d’accès basé sur les rôles (RBAC) et les JSON Web Tokens (JWTs) émis par Logto.
Avant de commencer
Vos applications clientes doivent obtenir des jetons d’accès (Access tokens) auprès de Logto. Si vous n'avez pas encore configuré l'intégration côté client, consultez nos Démarrages rapides pour React, Vue, Angular ou d'autres frameworks clients, ou consultez notre Guide machine à machine pour l'accès serveur à serveur.
Ce guide se concentre sur la validation côté serveur de ces jetons dans votre application Ruby on Rails.

Ce que vous allez apprendre
- Validation JWT : Apprenez à valider les jetons d’accès (Access tokens) et à extraire les informations d’authentification (Authentication)
- Implémentation de middleware : Créez un middleware réutilisable pour la protection de votre API
- Modèles de permissions : Comprenez et implémentez différents schémas d’autorisation (Authorization) :
- Ressources API globales pour les points de terminaison à l’échelle de l’application
- Permissions d’organisation pour le contrôle des fonctionnalités spécifiques à un locataire
- Ressources API au niveau de l’organisation pour l’accès aux données multi-locataires
- Intégration RBAC : Appliquez des permissions et des portées (Scopes) basées sur les rôles (Roles) dans vos points de terminaison API
Prérequis
- Dernière version stable de Ruby installée
- Compréhension de base de Ruby on Rails et du développement d’API web
- Une application Logto configurée (voir Démarrages rapides si besoin)
Aperçu des modèles de permission
Avant de mettre en place une protection, choisissez le modèle de permission qui correspond à l’architecture de votre application. Cela s’aligne avec les trois principaux scénarios d’autorisation de Logto :
- Ressources API globales
- Permissions d’organisation (hors API)
- Ressources API au niveau de l’organisation

- Cas d’utilisation : Protéger les ressources API partagées à travers toute votre application (non spécifiques à une organisation)
- Type de jeton : Jeton d’accès (Access token) avec audience globale
- Exemples : APIs publiques, services principaux du produit, points de terminaison d’administration
- Idéal pour : Produits SaaS avec des APIs utilisées par tous les clients, microservices sans isolation de locataire
- En savoir plus : Protéger les ressources API globales

- Cas d’utilisation : Contrôler les actions spécifiques à l’organisation, les fonctionnalités de l’interface ou la logique métier (hors APIs)
- Type de jeton : Jeton d’organisation (Organization token) avec audience spécifique à l’organisation
- Exemples : Limitation de fonctionnalités, permissions du tableau de bord, contrôle d’invitation des membres
- Idéal pour : SaaS multi-locataires avec des fonctionnalités et des flux de travail propres à chaque organisation
- En savoir plus : Protéger les permissions d’organisation (hors API)

- Cas d’utilisation : Protéger les ressources API accessibles dans le contexte d’une organisation spécifique
- Type de jeton : Jeton d’organisation (Organization token) avec audience de ressource API + contexte d’organisation
- Exemples : APIs multi-locataires, points de terminaison de données à portée d’organisation, microservices spécifiques au locataire
- Idéal pour : SaaS multi-locataires où les données API sont limitées à l’organisation
- En savoir plus : Protéger les ressources API au niveau de l’organisation
💡 Choisissez votre modèle avant de continuer – la mise en œuvre fera référence à l’approche choisie tout au long de ce guide.
Étapes de préparation rapide
Configurer les ressources & permissions Logto
- Ressources API globales
- Permissions d'organisation (hors API)
- Ressources API au niveau de l'organisation
- Créer une ressource API : Rendez-vous sur Console → Ressources API et enregistrez votre API (par exemple,
https://api.votreapp.com
) - Définir les permissions : Ajoutez des portées comme
read:products
,write:orders
– voir Définir des ressources API avec des permissions - Créer des rôles globaux : Rendez-vous sur Console → Rôles et créez des rôles qui incluent vos permissions API – voir Configurer des rôles globaux
- Attribuer des rôles : Attribuez des rôles aux utilisateurs ou applications M2M qui ont besoin d'accéder à l'API
- Définir les permissions d'organisation : Créez des permissions d'organisation hors API comme
invite:member
,manage:billing
dans le modèle d'organisation - Configurer les rôles d'organisation : Configurez le modèle d'organisation avec des rôles spécifiques à l'organisation et attribuez-leur des permissions
- Attribuer des rôles d'organisation : Attribuez des utilisateurs aux rôles d'organisation dans chaque contexte d'organisation
- Créer une ressource API : Enregistrez votre ressource API comme ci-dessus, mais elle sera utilisée dans le contexte d'organisation
- Définir les permissions : Ajoutez des portées comme
read:data
,write:settings
qui sont limitées au contexte d'organisation - Configurer le modèle d'organisation : Configurez des rôles d'organisation qui incluent les permissions de votre ressource API
- Attribuer des rôles d'organisation : Attribuez des utilisateurs ou applications M2M à des rôles d'organisation qui incluent des permissions API
- Configuration multi-locataire : Assurez-vous que votre API peut gérer les données et la validation à l'échelle de l'organisation
Commencez avec notre guide du contrôle d’accès basé sur les rôles (RBAC) pour des instructions d'installation étape par étape.
Mettez à jour votre application cliente
Demandez les portées appropriées dans votre client :
- Authentification utilisateur : Mettez à jour votre application → pour demander les portées de votre API et/ou le contexte d'organisation
- Machine à machine : Configurer les portées M2M → pour l'accès serveur à serveur
Le processus consiste généralement à mettre à jour la configuration de votre client pour inclure un ou plusieurs des éléments suivants :
- Paramètre
scope
dans les flux OAuth - Paramètre
resource
pour l'accès à la ressource API organization_id
pour le contexte d'organisation
Assurez-vous que l'utilisateur ou l'application M2M que vous testez s'est vu attribuer les rôles ou rôles d'organisation appropriés incluant les permissions nécessaires pour votre API.
Initialiser votre projet API
Pour initialiser un nouveau projet API Rails, vous pouvez utiliser le générateur Rails :
rails new your-api-name --api
cd your-api-name
Démarrez le serveur de développement :
rails server
Créez un contrôleur API de base :
class Api::BaseController < ApplicationController
def index
render json: { message: 'Hello from Rails API' }
end
end
Ajoutez les routes :
Rails.application.routes.draw do
namespace :api do
root 'base#index'
end
end
Consultez la documentation Rails pour plus de détails sur la configuration des contrôleurs, des modèles et d'autres fonctionnalités.
Initialiser les constantes et utilitaires
Définissez les constantes et utilitaires nécessaires dans votre code pour gérer l’extraction et la validation du jeton. Une requête valide doit inclure un en-tête Authorization
sous la forme Bearer <jeton d’accès (access token)>
.
module AuthConstants
JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks'
ISSUER = 'https://your-tenant.logto.app/oidc'
end
class AuthInfo
attr_accessor :sub, :client_id, :organization_id, :scopes, :audience
def initialize(sub, client_id = nil, organization_id = nil, scopes = [], audience = [])
@sub = sub
@client_id = client_id
@organization_id = organization_id
@scopes = scopes
@audience = audience
end
def to_h
{
sub: @sub,
client_id: @client_id,
organization_id: @organization_id,
scopes: @scopes,
audience: @audience
}
end
end
class AuthorizationError < StandardError
attr_reader :status
def initialize(message, status = 403)
super(message)
@status = status
end
end
module AuthHelpers
def extract_bearer_token(request)
authorization = request.headers['Authorization']
raise AuthorizationError.new('Authorization header is missing', 401) unless authorization
raise AuthorizationError.new('Authorization header must start with "Bearer "', 401) unless authorization.start_with?('Bearer ')
authorization[7..-1] # Supprimer le préfixe 'Bearer '
end
end
Récupérer les informations sur votre tenant Logto
Vous aurez besoin des valeurs suivantes pour valider les jetons émis par Logto :
- URI JSON Web Key Set (JWKS) : L’URL vers les clés publiques de Logto, utilisée pour vérifier les signatures JWT.
- Émetteur (Issuer) : La valeur attendue de l’émetteur (l’URL OIDC de Logto).
Commencez par trouver l’endpoint de votre tenant Logto. Vous pouvez le trouver à différents endroits :
- Dans la Console Logto, sous Paramètres → Domaines.
- Dans les paramètres de toute application que vous avez configurée dans Logto, Paramètres → Endpoints & Credentials.
Récupérer depuis l’endpoint de découverte OpenID Connect
Ces valeurs peuvent être récupérées depuis l’endpoint de découverte OpenID Connect de Logto :
https://<your-logto-endpoint>/oidc/.well-known/openid-configuration
Voici un exemple de réponse (autres champs omis pour plus de clarté) :
{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}
Codage en dur dans votre code (non recommandé)
Puisque Logto ne permet pas de personnaliser l’URI JWKS ou l’émetteur (Issuer), vous pouvez coder ces valeurs en dur dans votre code. Cependant, cela n’est pas recommandé pour les applications en production, car cela peut augmenter la charge de maintenance si une configuration change à l’avenir.
- URI JWKS :
https://<your-logto-endpoint>/oidc/jwks
- Émetteur (Issuer) :
https://<your-logto-endpoint>/oidc
Valider le jeton et les permissions
Après avoir extrait le jeton et récupéré la configuration OIDC, validez les éléments suivants :
- Signature : Le JWT doit être valide et signé par Logto (via JWKS).
- Émetteur (Issuer) : Doit correspondre à l’émetteur de votre tenant Logto.
- Audience (Audience) : Doit correspondre à l’indicateur de ressource de l’API enregistré dans Logto, ou au contexte d’organisation si applicable.
- Expiration : Le jeton ne doit pas être expiré.
- Permissions (Portées / scopes) : Le jeton doit inclure les portées requises pour votre API / action. Les portées sont des chaînes séparées par des espaces dans la revendication
scope
. - Contexte d’organisation : Si vous protégez des ressources API au niveau organisation, validez la revendication
organization_id
.
Consultez JSON Web Token pour en savoir plus sur la structure et les revendications des JWT.
À vérifier selon chaque modèle de permission
- Ressources API globales
- Permissions d'organisation (hors API)
- Ressources API au niveau organisation
- Revendication Audience (
aud
) : Indicateur de ressource API - Revendication Organisation (
organization_id
) : Non présent - Portées (permissions) à vérifier (
scope
) : Permissions de ressource API
- Revendication Audience (
aud
) :urn:logto:organization:<id>
(le contexte d'organisation est dansaud
) - Revendication Organisation (
organization_id
) : Non présent - Portées (permissions) à vérifier (
scope
) : Permissions d'organisation
- Revendication Audience (
aud
) : Indicateur de ressource API - Revendication Organisation (
organization_id
) : ID de l'organisation (doit correspondre à la requête) - Portées (permissions) à vérifier (
scope
) : Permissions de ressource API
Pour les permissions d’organisation hors API, le contexte d’organisation est représenté par la
revendication aud
(par exemple, urn:logto:organization:abc123
). La revendication
organization_id
n’est présente que pour les jetons de ressource API au niveau organisation.
Validez toujours à la fois les permissions (portées / scopes) et le contexte (audience, organisation) pour sécuriser les API multi-tenant.
Ajouter la logique de validation
Nous utilisons la gem jwt pour valider les JWT. Ajoutez-la à votre Gemfile :
gem 'jwt'
# net-http fait partie de la bibliothèque standard Ruby depuis Ruby 2.7, inutile de l’ajouter explicitement
Puis exécutez :
bundle install
Ajoutez d'abord ces utilitaires partagés pour gérer les JWKS et la validation des jetons :
require 'jwt'
require 'net/http'
require 'json'
class JwtValidator
include AuthHelpers
def self.fetch_jwks
@jwks ||= begin
uri = URI(AuthConstants::JWKS_URI)
response = Net::HTTP.get_response(uri)
raise AuthorizationError.new('Failed to fetch JWKS', 401) unless response.is_a?(Net::HTTPSuccess)
jwks_data = JSON.parse(response.body)
JWT::JWK::Set.new(jwks_data)
end
end
def self.validate_jwt(token)
jwks = fetch_jwks
# Laissez la bibliothèque JWT gérer la détection de l’algorithme à partir du JWKS
decoded_token = JWT.decode(token, nil, true, {
iss: AuthConstants::ISSUER,
verify_iss: true,
verify_aud: false, # Nous vérifierons l’audience manuellement selon le modèle de permission
jwks: jwks
})[0]
verify_payload(decoded_token)
decoded_token
end
def self.create_auth_info(payload)
scopes = payload['scope']&.split(' ') || []
audience = payload['aud'] || []
AuthInfo.new(
payload['sub'],
payload['client_id'],
payload['organization_id'],
scopes,
audience
)
end
def self.verify_payload(payload)
# Implémentez ici votre logique de vérification selon le modèle de permission
# Cela sera montré dans la section sur les modèles de permission ci-dessous
end
end
Ensuite, implémentez le middleware pour vérifier le jeton d’accès (access token) :
module JwtAuthentication
extend ActiveSupport::Concern
include AuthHelpers
included do
before_action :verify_access_token, only: [:protected_action] # Ajouter des actions spécifiques
end
private
def verify_access_token
begin
token = extract_bearer_token(request)
decoded_token = JwtValidator.validate_jwt(token)
# Stocker les informations d'authentification pour une utilisation générique
@auth = JwtValidator.create_auth_info(decoded_token)
rescue AuthorizationError => e
render json: { error: e.message }, status: e.status
rescue JWT::DecodeError, JWT::VerificationError, JWT::ExpiredSignature => e
render json: { error: 'Jeton invalide' }, status: 401
end
end
end
Selon votre modèle de permission, implémentez la logique de vérification appropriée dans JwtValidator
:
- Ressources API globales
- Permissions d’organisation (hors API)
- Ressources API au niveau de l’organisation
def self.verify_payload(payload)
# Vérifiez que la revendication d’audience correspond à votre indicateur de ressource API
audiences = payload['aud'] || []
unless audiences.include?('https://your-api-resource-indicator')
raise AuthorizationError.new('Invalid audience')
end
# Vérifiez les portées requises pour les ressources API globales
required_scopes = ['api:read', 'api:write'] # Remplacez par vos portées requises
token_scopes = payload['scope']&.split(' ') || []
unless required_scopes.all? { |scope| token_scopes.include?(scope) }
raise AuthorizationError.new('Insufficient scope')
end
end
def self.verify_payload(payload)
# Vérifiez que la revendication d’audience correspond au format d’organisation
audiences = payload['aud'] || []
has_org_audience = audiences.any? { |aud| aud.start_with?('urn:logto:organization:') }
unless has_org_audience
raise AuthorizationError.new('Invalid audience for organization permissions')
end
# Vérifiez que l’ID d’organisation correspond au contexte (vous devrez peut-être l’extraire du contexte de la requête)
expected_org_id = 'your-organization-id' # À extraire du contexte de la requête
expected_aud = "urn:logto:organization:#{expected_org_id}"
unless audiences.include?(expected_aud)
raise AuthorizationError.new('Organization ID mismatch')
end
# Vérifiez les portées requises pour l’organisation
required_scopes = ['invite:users', 'manage:settings'] # Remplacez par vos portées requises
token_scopes = payload['scope']&.split(' ') || []
unless required_scopes.all? { |scope| token_scopes.include?(scope) }
raise AuthorizationError.new('Insufficient organization scope')
end
end
def self.verify_payload(payload)
# Vérifiez que la revendication d’audience correspond à votre indicateur de ressource API
audiences = payload['aud'] || []
unless audiences.include?('https://your-api-resource-indicator')
raise AuthorizationError.new('Invalid audience for organization-level API resources')
end
# Vérifiez que l’ID d’organisation correspond au contexte (vous devrez peut-être l’extraire du contexte de la requête)
expected_org_id = 'your-organization-id' # À extraire du contexte de la requête
org_id = payload['organization_id']
unless expected_org_id == org_id
raise AuthorizationError.new('Organization ID mismatch')
end
# Vérifiez les portées requises pour les ressources API au niveau de l’organisation
required_scopes = ['api:read', 'api:write'] # Remplacez par vos portées requises
token_scopes = payload['scope']&.split(' ') || []
unless required_scopes.all? { |scope| token_scopes.include?(scope) }
raise AuthorizationError.new('Insufficient organization-level API scopes')
end
end
Appliquer le middleware à votre API
Appliquez maintenant le middleware à vos routes API protégées.
class ApplicationController < ActionController::API # Pour les applications API uniquement
# class ApplicationController < ActionController::Base # Pour les applications Rails complètes
include JwtAuthentication
end
class Api::ProtectedController < ApplicationController
before_action :verify_access_token
def index
# Accéder aux informations d'authentification depuis @auth
render json: { auth: @auth.to_h }
end
end
Rails.application.routes.draw do
namespace :api do
resources :protected, only: [:index]
end
end
Tester votre API protégée
Obtenir des jetons d’accès (Access tokens)
Depuis votre application cliente :
Si vous avez configuré une intégration client, votre application peut obtenir automatiquement les jetons. Extrayez le jeton d’accès (access token) et utilisez-le dans les requêtes API.
Pour tester avec curl / Postman :
-
Jetons utilisateur : Utilisez les outils développeur de votre application cliente pour copier le jeton d’accès depuis le localStorage ou l’onglet réseau.
-
Jetons machine à machine : Utilisez le flux d’identifiants client (client credentials flow). Voici un exemple non normatif utilisant curl :
curl -X POST https://your-tenant.logto.app/oidc/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=your-m2m-client-id" \
-d "client_secret=your-m2m-client-secret" \
-d "resource=https://your-api-resource-indicator" \
-d "scope=api:read api:write"Vous devrez peut-être ajuster les paramètres
resource
etscope
selon votre ressource API (API resource) et vos permissions ; un paramètreorganization_id
peut également être requis si votre API est liée à une organisation.
Besoin d’inspecter le contenu du jeton ? Utilisez notre décodificateur JWT pour décoder et vérifier vos JWT.
Tester les points de terminaison protégés
Requête avec jeton valide
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected
Réponse attendue :
{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Jeton manquant
curl http://localhost:3000/api/protected
Réponse attendue (401) :
{
"error": "Authorization header is missing"
}
Jeton invalide
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected
Réponse attendue (401) :
{
"error": "Invalid token"
}
Tests spécifiques au modèle de permission
- Ressources API globales
- Permissions d’organisation (hors API)
- Ressources API au niveau organisation
Scénarios de test pour les API protégées par des portées globales :
- Portées valides : Testez avec des jetons qui incluent les portées API requises (par exemple,
api:read
,api:write
) - Portées manquantes : Attendez-vous à une réponse 403 Interdit si le jeton ne contient pas les portées requises
- Audience incorrecte : Attendez-vous à une réponse 403 Interdit si l’audience ne correspond pas à la ressource API
# Jeton sans les portées requises - attendre 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected
Scénarios de test pour le contrôle d’accès spécifique à une organisation :
- Jeton d’organisation valide : Testez avec des jetons qui incluent le bon contexte d’organisation (ID d’organisation et portées)
- Portées manquantes : Attendez-vous à une réponse 403 Interdit si l’utilisateur n’a pas les permissions pour l’action demandée
- Mauvaise organisation : Attendez-vous à une réponse 403 Interdit si l’audience ne correspond pas au contexte d’organisation (
urn:logto:organization:<organization_id>
)
# Jeton pour une mauvaise organisation - attendre 403
curl -H "Authorization: Bearer token-for-different-organization" \
http://localhost:3000/api/protected
Scénarios de test combinant la validation de ressource API avec le contexte d’organisation :
- Organisation valide + portées API : Testez avec des jetons ayant à la fois le contexte d’organisation et les portées API requises
- Portées API manquantes : Attendez-vous à une réponse 403 Interdit si le jeton d’organisation ne possède pas les permissions API requises
- Mauvaise organisation : Attendez-vous à une réponse 403 Interdit lors de l’accès à l’API avec un jeton d’une autre organisation
- Audience incorrecte : Attendez-vous à une réponse 403 Interdit si l’audience ne correspond pas à la ressource API au niveau organisation
# Jeton d’organisation sans portées API - attendre 403
curl -H "Authorization: Bearer organization-token-without-api-scopes" \
http://localhost:3000/api/protected
Pour aller plus loin
RBAC en pratique : Mettre en œuvre une autorisation sécurisée pour votre application
Construire une application SaaS multi-locataires : Guide complet de la conception à la mise en œuvre