Partager via


Tutoriel : Créer et sécuriser une API web ASP.NET Core avec la plateforme d’identités Microsoft

S’applique à :Cercle vert avec un symbole de coche blanche. Locataires de main-d’œuvre Cercle vert avec un symbole de coche blanche. Locataires externes (en savoir plus)

Cette série de tutoriels montre comment protéger une API web ASP.NET Core avec la plateforme d’identités Microsoft pour limiter son accès aux seuls utilisateurs et applications clientes autorisés. L’API web que vous générez utilise les autorisations déléguées (étendues) et les autorisations d’application (rôles d’application).

Dans ce tutoriel, vous allez :

  • Créer une API web ASP.NET Core
  • Configurer l’API web pour qu’elle utilise les détails de l’inscription de l’application Microsoft Entra
  • Protéger vos points de terminaison d’API web
  • Exécutez l’API web pour vous assurer qu’elle écoute les requêtes HTTP

Conditions préalables

Créer un projet d’API web core ASP.NET

Pour créer un projet d’API web core ASP.NET minimal, procédez comme suit :

  1. Ouvrez votre terminal sur Visual Studio Code ou tout autre éditeur de code et accédez au répertoire dans lequel vous souhaitez créer votre projet.

  2. Exécutez les commandes suivantes sur l’interface CLI .NET ou tout autre outil en ligne de commande.

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. Sélectionnez Oui lorsqu’une boîte de dialogue vous demande si vous souhaitez approuver les auteurs.

  4. Sélectionnez Oui quand une boîte de dialogue vous demande si vous souhaitez ajouter des ressources requises au projet.

Installer les packages requis

Pour générer, protéger et tester l’API web ASP.NET Core, vous devez installer les packages suivants :

  • Microsoft.EntityFrameworkCore.InMemory- Package qui vous permet d’utiliser Entity Framework Core avec une base de données en mémoire. Il est utile à des fins de test, mais n’est pas conçu pour une utilisation en production.
  • Microsoft.Identity.Web - ensemble de bibliothèques principales ASP.NET qui simplifient l’ajout de la prise en charge de l’authentification et de l’autorisation aux applications web et aux API web qui s’intègrent à la plateforme d’identités Microsoft.

Pour installer le package, utilisez :

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Configurer les détails de l’inscription d’application

Ouvrez le fichier appsettings.json dans votre dossier d’application et ajoutez les détails d’inscription de l’application que vous avez enregistrés après l’inscription de l’API web.

{
    "AzureAd": {
        "Instance": "Enter_the_Authority_URL_Here",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here"
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

Remplacez les espaces réservés suivants, comme indiqué :

  • Remplacez Enter_the_Application_Id_Here par votre ID d’application (client).
  • Remplacez Enter_the_Tenant_Id_Here par votre ID de répertoire (locataire).
  • Remplacez Enter_the_Authority_URL_Here par votre URL d’autorité, comme expliqué dans la section suivante.

URL de l’autorité pour votre application

L’URL de l’autorité spécifie le répertoire à partir duquel microsoft Authentication Library (MSAL) peut demander des jetons. Vous la générez différemment dans les locataires externes et les employés, comme indiqué :

//Instance for workforce tenant
Instance: "https://login.microsoftonline.com/"

Utiliser un domaine d’URL personnalisé (facultatif)

Les domaines d’URL personnalisés ne sont pas pris en charge dans les tenants de main-d’œuvre.

Ajouter des autorisations

Toutes les API doivent publier au moins une portée, également appelée permission déléguée, pour que les applications clientes puissent obtenir un jeton d’accès pour un utilisateur. Les API doivent également publier au moins un rôle d’application, également appelé Autorisations d’application, pour que les applications clientes obtiennent un jeton d’accès comme elles-mêmes, c’est-à-dire lorsqu’elles ne connectent pas un utilisateur.

Nous spécifions ces autorisations dans le fichier appsettings.json. Dans ce tutoriel, vous avez inscrit les autorisations déléguées et d’application suivantes :

  • Autorisations déléguées :ToDoList.Read et ToDoList.ReadWrite.
  • Autorisations des applications :ToDoList.Read.All et ToDoList.ReadWrite.All.

Lorsqu’un utilisateur ou une application cliente appelle l’API web, seuls les clients disposant de ces étendues ou autorisations sont autorisés à accéder au point de terminaison protégé.

{
  "AzureAd": {
    "Instance": "Enter_the_Authority_URL_Here",
    "TenantId": "Enter_the_Tenant_Id_Here",
    "ClientId": "Enter_the_Application_Id_Here",
    "Scopes": {
      "Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
      "Write": ["ToDoList.ReadWrite"]
    },
    "AppPermissions": {
      "Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
      "Write": ["ToDoList.ReadWrite.All"]
    }
  },
  "Logging": {...},
  "AllowedHosts": "*"
}

Implémenter l’authentification et l’autorisation dans l’API

Pour configurer l’authentification et l’autorisation, ouvrez le program.cs fichier et remplacez son contenu par les extraits de code suivants :

Ajouter un schéma d’authentification

Dans cette API, nous utilisons le schéma du porteur JSON Web Token (JWT) comme mécanisme d’authentification par défaut. Utilisez la méthode AddAuthentication pour enregistrer le schéma porteur JWT.

// Add required packages to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add an authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

Créer le modèle de votre application

Dans le dossier racine du projet, créez un dossier appelé Models. Accédez au dossier Models et créez un fichier nommé ToDo.cs , puis ajoutez le code suivant.

using System;

namespace ToDoListAPI.Models;

public class ToDo
{
    public int Id { get; set; }
    public Guid Owner { get; set; }
    public string Description { get; set; } = string.Empty;
}

Le code précédent crée un modèle appelé ToDo. Ce modèle représente les données que l’application gère.

Ajouter un contexte de base de données

Ensuite, nous définissons une classe de contexte de base de données, qui coordonne la fonctionnalité Entity Framework pour un modèle de données. Cette classe hérite de la classe Microsoft.EntityFrameworkCore.DbContext qui gère les interactions entre l’application et la base de données. Pour ajouter le contexte de base de données, procédez comme suit :

  1. Créez un dossier appelé DbContext dans le dossier racine de votre projet.

  2. Accédez au dossier DbContext et créez un fichier nommé ToDoContext.cs , puis ajoutez le code suivant :

    using Microsoft.EntityFrameworkCore;
    using ToDoListAPI.Models;
    
    namespace ToDoListAPI.Context;
    
    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options) : base(options)
        {
        }
    
        public DbSet<ToDo> ToDos { get; set; }
    }
    
  3. Ouvrez le fichier Program.cs dans le dossier racine de votre projet et mettez-le à jour avec le code suivant :

    // Add the following to your imports
    using ToDoListAPI.Context;
    using Microsoft.EntityFrameworkCore;
    
    //Register ToDoContext as a service in the application
    builder.Services.AddDbContext<ToDoContext>(opt =>
        opt.UseInMemoryDatabase("ToDos"));
    

Dans l’extrait de code précédent, nous inscrivons le contexte de base de données en tant que service étendu dans le fournisseur de services d’application core ASP.NET (également appelé conteneur d’injection de dépendances). Vous configurez également la ToDoContext classe pour utiliser une base de données en mémoire pour l’API ToDo List.

Configurer un contrôleur

Les contrôleurs implémentent généralement les actions Create, Read, Update et Delete (CRUD) pour gérer les ressources. Étant donné que ce tutoriel se concentre davantage sur la protection des points de terminaison d’API, nous implémentons uniquement deux éléments d’action dans le contrôleur. Une action Lire tout pour récupérer tous les éléments To-Do et une action Créer pour ajouter un nouvel élément To-Do. Procédez comme suit pour ajouter un contrôleur à votre projet :

  1. Accédez au dossier racine de votre projet et créez un dossier nommé Controllers.

  2. Créez un fichier nommé ToDoListController.cs dans le dossier Controllers et ajoutez le code de plaque de chaudière suivant :

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
using ToDoListAPI.Models;
using ToDoListAPI.Context;

namespace ToDoListAPI.Controllers;

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ToDoListController : ControllerBase
{
    private readonly ToDoContext _toDoContext;

    public ToDoListController(ToDoContext toDoContext)
    {
        _toDoContext = toDoContext;
    }

    [HttpGet()]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> GetAsync(){...}

    [HttpPost]
    [RequiredScopeOrAppPermission()]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...}

    private bool RequestCanAccessToDo(Guid userId){...}

    private Guid GetUserId(){...}

    private bool IsAppMakingRequest(){...}
}

Ajouter du code au contrôleur

Cette section explique comment ajouter du code au contrôleur généré dans la section précédente. L’accent est mis ici sur la protection de l’API, pas sur sa création.

  1. Importez les packages nécessaires : Le Microsoft.Identity.Web package est un wrapper autour de MSAL.NET qui nous aide à gérer facilement la logique d’authentification, comme la gestion de la validation des jetons. Pour vous assurer que nos points d'accès nécessitent une autorisation, nous utilisons le package intégré Microsoft.AspNetCore.Authorization.

  2. Dans la mesure où nous avons accordé des autorisations pour que cette API soit appelée à l'aide d'autorisations déléguées au nom de l'utilisateur ou d'autorisations d'application permettant au client d'appeler en son propre nom plutôt qu'au nom de l'utilisateur, il est important de déterminer si l'appel est effectué par l'application en son propre nom. Pour ce faire, le moyen le plus simple consiste à déterminer si le jeton d’accès contient la idtyp revendication facultative. Cette revendication idtyp constitue la façon la plus simple pour une API de déterminer si un jeton est un jeton d’application ou un jeton d’application + un jeton d’utilisateur. Nous recommandons d’activer la revendication facultative idtyp.

    Si la revendication idtyp n’est pas activée, vous pouvez utiliser les revendications roles et scp pour déterminer si le jeton d’accès est un jeton d’application ou un jeton d’application + un jeton d’utilisateur. Un jeton d’accès émis par l’ID Microsoft Entra a au moins l’une des deux revendications. Les jetons d’accès émis pour un utilisateur ont la déclaration scp. Les jetons d’accès émis pour une application ont la revendication roles. Les jetons d’accès qui contiennent les deux revendications sont émis uniquement aux utilisateurs, où la revendication scp désigne les permissions déléguées, tandis que la revendication roles désigne le rôle de l’utilisateur. Les jetons d’accès qui n’ont ni l’un ni l’autre ne doivent pas être honorés.

    private bool IsAppMakingRequest()
    {
        if (HttpContext.User.Claims.Any(c => c.Type == "idtyp"))
        {
            return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app");
        }
        else
        {
            return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp");
        }
    }
    
  3. Ajoutez une fonction d’assistance permettant de déterminer si la requête en cours contient suffisamment d’autorisations pour effectuer l’action prévue. Vérifiez la validité de l'identifiant de l'utilisateur pour déterminer si l'application émet la requête en son propre nom ou si elle émet l'appel au nom d'un utilisateur détenteur de la ressource en question.

    private bool RequestCanAccessToDo(Guid userId)
        {
            return IsAppMakingRequest() || (userId == GetUserId());
        }
    
    private Guid GetUserId()
        {
            Guid userId;
            if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId))
            {
                throw new Exception("User ID is not valid.");
            }
            return userId;
        }
    
  4. Insérez vos définitions d’autorisation pour protéger les itinéraires. Protégez votre API en ajoutant l’attribut [Authorize] à la classe de contrôleur. Ceci garantit la possibilité d'appeler les actions du contrôleur uniquement si l'API est invoquée à l'aide d'une identité autorisée. Les définitions d’autorisations définissent les types d’autorisations nécessaires pour effectuer ces actions.

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController: ControllerBase{...}
    

    Ajoutez des autorisations aux points de terminaison GET et POST. Pour cela, utilisez la méthode RequiredScopeOrAppPermission appartenant à l’espace de noms Microsoft.Identity.Web.Resource. Transmettez ensuite les étendues et autorisations à cette méthode via les attributs RequiredScopesConfigurationKey et RequiredAppPermissionsConfigurationKey.

    [HttpGet]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Read",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read"
    )]
    public async Task<IActionResult> GetAsync()
    {
        var toDos = await _toDoContext.ToDos!
            .Where(td => RequestCanAccessToDo(td.Owner))
            .ToListAsync();
    
        return Ok(toDos);
    }
    
    [HttpPost]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Write",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write"
    )]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo)
    {
        // Only let applications with global to-do access set the user ID or to-do's
        var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId();
    
        var newToDo = new ToDo()
        {
            Owner = ownerIdOfTodo,
            Description = toDo.Description
        };
    
        await _toDoContext.ToDos!.AddAsync(newToDo);
        await _toDoContext.SaveChangesAsync();
    
        return Created($"/todo/{newToDo!.Id}", newToDo);
    }
    

Configurer l’intergiciel d’API pour utiliser le contrôleur

Ensuite, nous configurons l’application pour reconnaître et utiliser des contrôleurs pour gérer les requêtes HTTP. Ouvrez le program.cs fichier et ajoutez le code suivant pour inscrire les services de contrôleur dans le conteneur d’injection de dépendances.


builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();

app.Run();

Dans l’extrait de code précédent, la AddControllers() méthode prépare l’application à utiliser des contrôleurs en inscrivant les services nécessaires tout en MapControllers() mappant les itinéraires du contrôleur pour gérer les requêtes HTTP entrantes.

Exécuter votre API

Exécutez votre API pour vous assurer qu’elle s’exécute sans erreur à l’aide de la commande dotnet run. Si vous envisagez d'utiliser le protocole HTTPS même pour les tests, vous devez faire confiance au certificat de développement de .NET.

  1. Démarrez l’application en tapant ce qui suit dans le terminal :

    dotnet run
    
  2. Une sortie similaire à ce qui suit doit être affichée dans le terminal, ce qui confirme que l’application s’exécute sur http://localhost:{port} et écoute les demandes.

    Building...
    info: Microsoft.Hosting.Lifetime[0]
        Now listening on: http://localhost:{port}
    info: Microsoft.Hosting.Lifetime[0]
        Application started. Press Ctrl+C to shut down.
    ...
    

La page web http://localhost:{host} affiche une sortie similaire à l’image suivante. Cela est dû au fait que l’API est appelée sans authentification. Pour effectuer un appel autorisé, reportez-vous aux étapes suivantes pour obtenir des conseils sur l’accès à une API web protégée.

Capture d’écran montrant l’erreur 401 lorsque la page web est lancée.

Pour obtenir un exemple complet de ce code d’API, consultez le fichier d’exemples.

Étapes suivantes