Aller au contenu principal

Protégez votre API ASP.NET Core 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 ASP.NET Core 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 ASP.NET Core.

Une illustration montrant le focus de ce guide

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 .NET installée
  • Compréhension de base de ASP.NET Core 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 :

RBAC des ressources API globales
  • 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

💡 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

  1. Créer une ressource API : Rendez-vous sur Console → Ressources API et enregistrez votre API (par exemple, https://api.votreapp.com)
  2. Définir les permissions : Ajoutez des portées comme read:products, write:orders – voir Définir des ressources API avec des permissions
  3. 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
  4. Attribuer des rôles : Attribuez des rôles aux utilisateurs ou applications M2M qui ont besoin d'accéder à l'API
Nouveau sur le RBAC ?:

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 :

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
Avant de coder:

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 .NET Web API, vous pouvez utiliser le CLI .NET :

dotnet new webapi -n YourApiName
cd YourApiName

Ajoutez le package NuGet requis pour l’authentification JWT :

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Créez un contrôleur API de base :

Controllers/ApiController.cs
using Microsoft.AspNetCore.Mvc;

namespace YourApiName.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class ApiController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new { message = "Hello from .NET API" });
}
}
}

Démarrez le serveur de développement :

dotnet run
remarque:

Consultez la documentation ASP.NET Core pour plus de détails sur la configuration des contrôleurs, du middleware 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)>.

AuthConstants.cs
namespace YourApiNamespace
{
public static class AuthConstants
{
public const string Issuer = "https://your-tenant.logto.app/oidc";
}
}
AuthenticationExceptions.cs
namespace YourApiNamespace.Exceptions
{
public class AuthorizationException : Exception
{
public int StatusCode { get; }

public AuthorizationException(string message, int statusCode = 403) : base(message)
{
StatusCode = statusCode;
}
}
}

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ètresDomaines.
  • Dans les paramètres de toute application que vous avez configurée dans Logto, ParamètresEndpoints & 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"
}

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

  • Revendication Audience (aud) : Indicateur de ressource API
  • Revendication Organisation (organization_id) : Non présent
  • 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.

astuce:

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

Ajoutez le package NuGet requis pour l’authentification JWT :

<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />

Créez un service de validation pour gérer la validation du jeton :

JwtValidationService.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using YourApiNamespace.Exceptions;

namespace YourApiNamespace.Services
{
public interface IJwtValidationService
{
Task ValidateTokenAsync(TokenValidatedContext context);
}

public class JwtValidationService : IJwtValidationService
{
public async Task ValidateTokenAsync(TokenValidatedContext context)
{
var principal = context.Principal!;

try
{
// Ajoutez ici votre logique de validation basée sur le modèle de permission
ValidatePayload(principal);
}
catch (AuthorizationException)
{
throw; // Relancer les exceptions d'autorisation
}
catch (Exception ex)
{
throw new AuthorizationException($"Échec de la validation du jeton : {ex.Message}", 401);
}
}

private void ValidatePayload(ClaimsPrincipal principal)
{
// Implémentez ici votre logique de vérification basée sur le modèle de permission
// Ceci sera détaillé dans la section sur les modèles de permission ci-dessous
}
}
}

Configurez l’authentification JWT dans votre Program.cs :

Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using YourApiNamespace.Services;
using YourApiNamespace.Exceptions;

var builder = WebApplication.CreateBuilder(args);

// Ajoutez les services au conteneur
builder.Services.AddControllers();
builder.Services.AddScoped<IJwtValidationService, JwtValidationService>();

// Configurez l’authentification JWT
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = AuthConstants.Issuer;
options.MetadataAddress = $"{AuthConstants.Issuer}/.well-known/openid_configuration";
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = AuthConstants.Issuer,
ValidateAudience = false, // Nous validerons l’audience manuellement selon le modèle de permission
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(5)
};

options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var validationService = context.HttpContext.RequestServices
.GetRequiredService<IJwtValidationService>();

await validationService.ValidateTokenAsync(context);
},
OnAuthenticationFailed = context =>
{
// Gérer les erreurs de la bibliothèque JWT comme 401
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
context.Response.WriteAsync($"{{\"error\": \"Invalid token\"}}");
context.HandleResponse();
return Task.CompletedTask;
}
};
});

builder.Services.AddAuthorization();

var app = builder.Build();

// Gestion globale des erreurs pour les échecs d’authentification / autorisation
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (AuthorizationException ex)
{
context.Response.StatusCode = ex.StatusCode;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync($"{{\"error\": \"{ex.Message}\"}}");
}
});

// Configurez le pipeline de requêtes HTTP
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Selon votre modèle de permission, implémentez la logique de validation appropriée dans JwtValidationService :

JwtValidationService.cs
private void ValidatePayload(ClaimsPrincipal principal)
{
// Vérifiez que la revendication d’audience correspond à votre indicateur de ressource API
var audiences = principal.FindAll("aud").Select(c => c.Value).ToList();
if (!audiences.Contains("https://your-api-resource-indicator"))
{
throw new AuthorizationException("Audience invalide");
}

// Vérifiez les portées requises pour les ressources API globales
var requiredScopes = new[] { "api:read", "api:write" }; // Remplacez par vos portées requises
var tokenScopes = principal.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();

if (!requiredScopes.All(scope => tokenScopes.Contains(scope)))
{
throw new AuthorizationException("Portée insuffisante");
}
}

Appliquer le middleware à votre API

Appliquez maintenant le middleware à vos routes API protégées.

Nous avons déjà configuré les middlewares d’authentification (Authentication) et d’autorisation (Authorization) dans les sections précédentes. Nous pouvons maintenant créer un contrôleur protégé qui valide les jetons d’accès (Access tokens) et extrait les revendications (Claims) des requêtes authentifiées.

ProtectedController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;

namespace YourApiNamespace.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize] // Exige l’authentification pour toutes les actions de ce contrôleur
public class ProtectedController : ControllerBase
{
[HttpGet]
public IActionResult GetProtectedData()
{
// Informations du jeton d’accès (Access token) directement depuis les revendications (Claims) de l’utilisateur
var sub = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? User.FindFirst("sub")?.Value;
var clientId = User.FindFirst("client_id")?.Value;
var organizationId = User.FindFirst("organization_id")?.Value;
var scopes = User.FindFirst("scope")?.Value?.Split(' ') ?? Array.Empty<string>();
var audience = User.FindAll("aud").Select(c => c.Value).ToArray();

return Ok(new {
sub,
client_id = clientId,
organization_id = organizationId,
scopes,
audience
});
}

[HttpGet("claims")]
public IActionResult GetAllClaims()
{
// Retourne toutes les revendications (Claims) pour le débogage / l’inspection
var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToList();
return Ok(new { claims });
}
}
}

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 :

  1. 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.

  2. 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 et scope selon votre ressource API (API resource) et vos permissions ; un paramètre organization_id peut également être requis si votre API est liée à une organisation.

astuce:

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

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

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