Implémentez l’autorisation dans les API web avec Microsoft. Identity.Web

Dans cet article, vous implémentez l’autorisation dans ASP.NET Core API web à l’aide de Microsoft. Identity.Web. Vous allez valider les étendues (autorisations déléguées ) et les autorisations d’application (autorisations d’application ) pour contrôler l’accès aux ressources protégées. Les exemples utilisent Microsoft Entra ID comme fournisseur d’identité.

Comprendre les concepts d’autorisation

Cette section couvre les principales différences entre l'authentification et l'autorisation, et décrit ce que Microsoft.Identity.Web valide dans les jetons d'accès.

Authentification et autorisation

Concept Objectif Résultat
Authentification Vérifier l’identité 401 Non autorisé en cas d’échec
Autorisation Vérifier les autorisations 403 Interdit en cas d'insuffisance

Ce qui est validé

Lorsqu’une API web reçoit un jeton d’accès, Microsoft. Identity.Web valide :

  1. Signature de jeton - Est-ce d’une autorité approuvée ?
  2. Audience de jeton - Est-elle destinée à cette API ?
  3. Expiration du jeton - Est-il toujours valide ?
  4. Étendues/rôles : l’application cliente et l’objet (utilisateur) disposent-ils des autorisations appropriées ?

Ce guide se concentre sur #4 : validation des étendues et des autorisations d’application.

Étendues (autorisations déléguées)

Les portées s’appliquent lorsqu’un utilisateur accorde une autorisation à une application pour agir en son nom (par exemple, une API Internet appelée pour le compte d’un utilisateur connecté).

Détails Valeur
Revendication de jeton scp ou scope (application cliente) ; roles (utilisateur)
Exemples de valeurs "access_as_user", "User.Read", "Files.ReadWrite"

Autorisations d’application (autorisations d’application)

Les autorisations d’application s’appliquent lorsqu’une application appelle l’API web comme elle-même sans contexte utilisateur, comme un démon ou un service en arrière-plan à l’aide des informations d’identification du client.

Détails Valeur
Revendication de jeton roles
Exemples de valeurs "Mail.Read.All", "User.Read.All"

Valider les étendues avec RequiredScope

L’attribut RequiredScope vérifie que le jeton d’accès contient au moins une des étendues spécifiées. Utilisez cet attribut lorsque votre API traite uniquement les requêtes déléguées par l’utilisateur.

Configurer la validation du périmètre

Suivez ces étapes pour activer la validation de l’étendue dans votre API.

1. Activez l’autorisation dans votre API :

Ajoutez des services d’authentification et d’autorisation à votre pipeline d’application :

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. Protégez les contrôleurs ou les actions :

Appliquez les attributs [Authorize] et [RequiredScope] à votre contrôleur ou des actions individuelles.

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

Appliquer des modèles de portée

Choisissez le modèle qui correspond le mieux à la façon dont vous gérez les étendues dans votre application.

Modèle 1 : périmètres (ou scopes) codés en dur

Utilisez ce modèle lorsque les étendues sont fixes et connues au moment du développement.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

Pour accepter l'une de plusieurs étendues, listez-les en tant que paramètres.

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

Modèle 2 : Étendues de la configuration

Utilisez ce modèle lorsque les étendues doivent être configurables par environnement. Définissez les étendues dans votre fichier de configuration :

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

Référencez la clé de configuration dans votre contrôleur :

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

Cette approche vous permet de modifier les étendues sans recompiler.

Modèle 3 : Contextes au niveau de l’action

Utilisez ce modèle lorsque différentes actions nécessitent des autorisations différentes. Appliquer [RequiredScope] à des méthodes d’action individuelles :

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

Comprendre le flux de validation

Lorsqu’une demande arrive, l’intergiciel le traite dans l’ordre suivant :

  1. ASP.NET Core middleware d’authentification valide le jeton
  2. RequiredScopevérification d'attributs pour la revendication scp ou scope
  3. Si le jeton contient au moins une portée correspondante, la requête se poursuit.
  4. Si aucune étendue correspondante n’est trouvée, l’API retourne une réponse 403 Interdit.

L’exemple suivant montre une réponse d’erreur classique :

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

Valider les autorisations d’application avec RequiredScopeOrAppPermission

L’attribut RequiredScopeOrAppPermission valide les étendues (déléguées ) ou les autorisations d’application (application). Utilisez cet attribut lorsque votre API sert à la fois des applications déléguées par l’utilisateur et des applications démon/service à partir du même point de terminaison.

Si votre API traite uniquement les demandes déléguées par l’utilisateur, utilisez RequiredScope plutôt.

Configurer la validation de l’étendue ou de l’autorisation d’application

Appliquez l’attribut pour accepter l’un ou l’autre type de jeton :

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

Configurer les autorisations d’application à partir des paramètres

Stocker les étendues et les autorisations d’application dans la configuration pour les modifier sans recompiler.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

Référencez les clés de configuration dans votre contrôleur :

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

Comparer les différences des déclarations de token

Le tableau suivant montre comment les revendications diffèrent entre les jetons délégués par l’utilisateur et les jetons d’application uniquement :

Type de jeton Réclamation Exemple de valeur
Délégué par l’utilisateur scp ou scope "access_as_user User.Read"
Application uniquement roles ["TodoList.ReadWrite.All"]

L’exemple suivant montre un jeton délégué par l’utilisateur :

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

L’exemple suivant montre un jeton d’application uniquement :

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

Créer des stratégies d’autorisation

Pour les scénarios d’autorisation complexes, utilisez ASP.NET Core stratégies d’autorisation. Les stratégies vous permettent de centraliser les règles, de combiner plusieurs exigences et d’écrire une logique d’autorisation testable.

Benefit Description
Logique centralisée Définir des règles d’autorisation une fois, réutiliser partout
Composable Combiner plusieurs exigences (étendues + revendications + logique personnalisée)
Testable Faciliter le test unitaire de la logique d'autorisation
Flexible Exigences spécifiques au-delà de la validation de la portée

Modèle 1 : Définir une stratégie avec RequireScope

Définissez des stratégies nommées qui nécessitent des étendues spécifiques, puis faites référence à celles-ci dans vos contrôleurs.

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

Appliquez les stratégies aux actions du contrôleur :

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

Modèle 2 : Définir une stratégie avec ScopeAuthorizationRequirement

Utilisez ScopeAuthorizationRequirement pour des exigences d’étendue plus explicites :

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

Modèle 3 : Définir une stratégie par défaut

Définissez automatiquement une stratégie par défaut qui s’applique à tous les [Authorize] attributs :

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

Chaque [Authorize] attribut nécessite désormais l’étendue access_as_user :

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

Modèle 4 : Combiner plusieurs exigences

Combinez les exigences d’étendue, de rôle et d’authentification dans une seule stratégie :

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

Modèle 5 : Créer une stratégie à partir de la configuration

Chargez les périmètres à partir de la configuration pour maintenir l'environnement spécifique des politiques.

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

Filtrer les demandes par locataire

Limitez l’accès d’API aux jetons provenant de locataires Microsoft Entra spécifiques. Cela est utile lorsque votre API multilocataire ne doit accepter que les demandes des locataires clients approuvés.

Restreindre l’accès aux locataires autorisés

Définissez une stratégie qui vérifie la revendication d’ID de locataire sur une liste verte :

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

Configurer le filtrage des locataires à partir des paramètres

Stockez les ID de locataire autorisés dans la configuration pour les gérer sans modification du code.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

Lisez la liste des locataires et créez la stratégie au démarrage :

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

Combiner des étendues avec le filtrage de locataire

Créez une politique qui requiert à la fois une portée valide et un client approuvé :

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

Suivre les bonnes pratiques

Appliquez ces recommandations pour créer une logique d’autorisation sécurisée et gérable.

Choses à faire

1. Associez toujours [Authorize] à la validation du périmètre :

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. Utilisez la configuration pour les étendues spécifiques à l’environnement :

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. Appliquez le privilège minimum :

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. Utilisez des stratégies pour une autorisation complexe :

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. Activez des réponses d’erreur détaillées en développement :

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

Choses à ne pas faire

1. Ne pas ignorer [Authorize] lors de l’utilisation de RequiredScope :

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. Ne codez pas en dur les ID de locataire en production :

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. Ne confondez pas les périmètres avec les rôles :

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. N’exposez pas les informations d’étendue sensibles dans les messages d’erreur de production :

Configurez les niveaux de journalisation appropriés et la gestion des erreurs pour les environnements de production.


Résoudre les problèmes d’autorisation

Utilisez les instructions suivantes pour diagnostiquer les problèmes d’autorisation courants.

403 Interdit - Étendue manquante

Erreur: L’API retourne 403 même avec un jeton valide.

Diagnostic:

  1. Décodez le jeton à jwt.ms.
  2. Vérifiez la revendication scp ou scope.
  3. Vérifiez que la valeur correspond à votre RequiredScope attribut.

Solution:

  • Vérifiez que l'application client demande le périmètre correct lors de l'acquisition du jeton.
  • Vérifiez que l’étendue est exposée dans l’inscription de l’application API dans Microsoft Entra.
  • Accordez le consentement de l’administrateur si nécessaire.

RequiredScope ne fonctionne pas

Symptôme: L’attribut semble être ignoré.

Vérification:

  1. Avez-vous ajouté l’attribut [Authorize] ?
  2. app.UseAuthorization() est-il appelé après app.UseAuthentication() ?
  3. Est-il services.AddAuthorization() inscrit ?

Clé de configuration introuvable

Erreur : La validation de l’étendue échoue silencieusement.

Vérification:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

Vérifiez que le chemin de configuration correspond exactement.