Condividi tramite


Esercitazione: Creare e proteggere un'API Web ASP.NET Core con Microsoft Identity Platform

Si applica a: Cerchio verde con un simbolo di spunta bianco che indica che il contenuto seguente si applica ai tenant della forza lavoro. I tenant della forza lavoro Cerchio verde con un simbolo di spunta bianco che indica che il contenuto seguente si applica ai tenant esterni. I tenant esterni (Altre informazioni)

Questa serie di esercitazioni illustra come proteggere un'API Web ASP.NET Core con Microsoft Identity Platform per limitare l'accesso solo agli utenti autorizzati e alle app client. L'API Web compilata usa autorizzazioni delegate (ambiti) e autorizzazioni dell'applicazione (ruoli dell'app).

In questa esercitazione, farai:

  • Creare un'API Web ASP.NET Core
  • Configurare l'API Web per utilizzare i dettagli di registrazione dell'app di Microsoft Entra
  • Proteggere gli endpoint dell'API Web
  • Eseguire l'API Web per assicurarsi che sia in ascolto delle richieste HTTP

Prerequisiti

Creare un nuovo progetto API Web di ASP.NET Core

Per creare un progetto API Web core ASP.NET minimo, seguire questa procedura:

  1. Aprire il terminale in Visual Studio Code o in qualsiasi altro editor di codice e passare alla directory in cui si vuole creare il progetto.

  2. Eseguire i comandi seguenti nell'interfaccia della riga di comando di .NET o in qualsiasi altro strumento da riga di comando.

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. Selezionare quando una finestra di dialogo chiede se si vuole considerare attendibili gli autori.

  4. Selezionare Quando una finestra di dialogo chiede se si desidera aggiungere gli asset necessari al progetto.

Installare i pacchetti necessari

Per compilare, proteggere e testare l'API Web ASP.NET Core, è necessario installare i pacchetti seguenti:

  • Microsoft.EntityFrameworkCore.InMemory- Pacchetto che consente di usare Entity Framework Core con un database in memoria. È utile a scopo di test, ma non è progettato per l'uso in produzione.
  • Microsoft.Identity.Web : un set di librerie core di ASP.NET che semplificano l'aggiunta del supporto di autenticazione e autorizzazione alle app Web e alle API Web che si integrano con Microsoft Identity Platform.

Per installare il pacchetto, usare:

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

Configurare i dettagli di registrazione dell'app

Aprire il file appsettings.json nella cartella dell'app e aggiungere i dettagli di registrazione dell'app registrati dopo la registrazione dell'API Web.

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

Sostituire i segnaposto seguenti come illustrato:

  • Sostituire Enter_the_Application_Id_Here con l'ID applicazione (client).
  • Sostituire Enter_the_Tenant_Id_Here con l'ID della directory (tenant).
  • Sostituire Enter_the_Authority_URL_Here con l'URL dell'autorità, come illustrato nella sezione successiva.

URL dell'autorità per l'app

L'URL dell'autorità specifica la directory da cui Microsoft Authentication Library (MSAL) può richiedere token. Lo costruisci in modo diverso sia nella forza lavoro che negli affittuari esterni, come illustrato di seguito.

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

Usare un dominio URL personalizzato (facoltativo)

I domini URL personalizzati non sono supportati nelle utenze aziendali.

Aggiungere autorizzazioni

Tutte le API devono pubblicare almeno un ambito, detto anche autorizzazione delegata, affinché le app client ottengano correttamente un token di accesso per un utente. Le API devono anche pubblicare almeno un ruolo dell'applicazione, detto anche autorizzazioni dell'applicazione, affinché le app client ottengano un token di accesso in qualità di sé stesse, ovvero quando non effettuano l'accesso come utente.

Queste autorizzazioni vengono specificate nel file appsettings.json. In questa esercitazione sono state registrate le seguenti autorizzazioni delegate e dell'applicazione:

  • Autorizzazioni delegate:ToDoList.Read e ToDoList.ReadWrite.
  • Autorizzazioni delle applicazioni:ToDoList.Read.All e ToDoList.ReadWrite.All.

Quando un utente o un'applicazione client chiama l'API Web, solo i client con questi ambiti o le autorizzazioni vengono autorizzati ad accedere all'endpoint protetto.

{
  "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": "*"
}

Implementare l'autenticazione e l'autorizzazione nell'API

Per configurare l'autenticazione e l'autorizzazione, aprire il file program.cs e sostituirne il contenuto nei frammenti di codice seguenti:

Aggiungere uno schema di autenticazione

In questa API viene usato lo schema di connessione JWT (JSON Web Token) come meccanismo di autenticazione predefinito. Usare il metodo AddAuthentication per registrare lo schema bearer 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);

Creare il modello dell'app

Nella cartella radice del progetto creare una cartella denominata Models. Passare alla cartella Models e creare un file denominato ToDo.cs e quindi aggiungere il codice seguente.

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;
}

Il codice precedente crea un modello denominato ToDo. Questo modello rappresenta i dati gestiti dall'app.

Aggiungere un contesto di database

Verrà quindi definita una classe di contesto del database, che coordina la funzionalità di Entity Framework per un modello di dati. Questa classe eredita dalla classe Microsoft.EntityFrameworkCore.DbContext che gestisce le interazioni tra l'applicazione e il database. Per aggiungere il contesto del database, seguire questa procedura:

  1. Creare una cartella denominata DbContext nella cartella radice del progetto.

  2. Passare alla cartella DbContext e creare un file denominato ToDoContext.cs e quindi aggiungere il codice seguente:

    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. Aprire il file Program.cs nella cartella radice del progetto e aggiornarlo con il codice seguente:

    // 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"));
    

Nel frammento di codice precedente, il contesto di database viene registrato come servizio a livello di ambito nel provider di servizi dell'applicazione ASP.NET Core (noto anche come contenitore di iniezione delle dipendenze). È anche possibile configurare la ToDoContext classe per l'uso di un database in memoria per l'API Elenco ToDo.

Configurare un controller

I controller implementano in genere azioni Create, Read, Update e Delete (CRUD) per gestire le risorse. Poiché questa esercitazione è incentrata maggiormente sulla protezione degli endpoint API, vengono implementati solo due elementi di azione nel controller. Azione "Leggi tutto" per recuperare tutti gli elementi To-Do e azione "Crea" per aggiungere un nuovo elemento To-Do. Seguire questa procedura per aggiungere un controller al progetto:

  1. Passare alla cartella radice del progetto e creare una cartella denominata Controllers.

  2. Creare un file denominato ToDoListController.cs all'interno della cartella Controllers e aggiungere il codice della lastra caldaia seguente:

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(){...}
}

Aggiungere codice al controller

In questa sezione si spiega come aggiungere codice al controller scaffolding creato nella sezione precedente. L'obiettivo è proteggere l'API, non crearla.

  1. Importare i pacchetti necessari: Il Microsoft.Identity.Web pacchetto è un wrapper intorno MSAL.NET che consente di gestire facilmente la logica di autenticazione, ad esempio la gestione della convalida dei token. Per assicurarsi che gli endpoint richiedano l'autorizzazione, viene usato il pacchetto predefinito Microsoft.AspNetCore.Authorization .

  2. Poiché sono state concesse autorizzazioni per chiamare questa API utilizzando autorizzazioni delegate per conto dell'utente o autorizzazioni dell'applicazione, dove il client agisce per proprio conto e non per conto dell'utente, è importante sapere se la chiamata viene effettuata dall'app in autonomia. Il modo più semplice per eseguire questa operazione consiste nel determinare se il token di accesso contiene l'attestazione idtyp facoltativa. Questa idtyp attestazione è il modo più semplice per l'API per determinare se un token è un token dell'app o un token app + utente. Si consiglia di abilitare la dichiarazione facoltativa idtyp.

    Se l'attestazione idtyp non è abilitata, è possibile usare le roles attestazioni e scp per determinare se il token di accesso è un token dell'app o un token utente e un'app. Un token di accesso rilasciato da Microsoft Entra ID ha almeno una delle due attestazioni. I token di accesso emessi a un utente hanno l'attestazione scp . I token di accesso rilasciati a un'applicazione hanno l'attestazione roles . I token di accesso che contengono entrambe le attestazioni vengono emessi solo agli utenti, in cui l'attestazione scp designa le autorizzazioni delegate, mentre l'attestazione roles designa il ruolo dell'utente. I token di accesso che non soddisfano alcuna delle condizioni non devono essere considerati validi.

    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. Aggiungere una funzione helper che determina se la richiesta effettuata contiene autorizzazioni sufficienti per eseguire l'azione desiderata. Controllare se è l'app che effettua la richiesta per proprio conto o se l'app effettua la chiamata per conto di un utente proprietario della risorsa specificata convalidando l'ID utente.

    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. Inserire le definizioni di autorizzazione per proteggere i percorsi. Proteggere l'API aggiungendo l'attributo [Authorize] alla classe controller. In questo modo, le azioni del controller possono essere chiamate solo se l'API viene chiamata con un'identità autorizzata. Le definizioni di autorizzazione definiscono i tipi di autorizzazioni necessari per eseguire queste azioni.

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

    Aggiungere autorizzazioni agli endpoint GET e POST. Eseguire questa operazione usando il metodo RequiredScopeOrAppPermission che fa parte dello spazio dei nomi Microsoft.Identity.Web.Resource . Si passano quindi ambiti e autorizzazioni a questo metodo tramite gli attributi RequiredScopesConfigurationKey e 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);
    }
    

Configurare il middleware API per l'uso del controller

Configurare quindi l'applicazione per riconoscere e usare i controller per la gestione delle richieste HTTP. Aprire il program.cs file e aggiungere il codice seguente per registrare i servizi controller nel contenitore di inserimento delle dipendenze.


builder.Services.AddControllers();

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

app.Run();

Nel frammento di codice precedente, il AddControllers() metodo prepara l'applicazione all'uso dei controller registrando i servizi necessari mentre MapControllers() esegue il mapping delle route del controller per gestire le richieste HTTP in ingresso.

Esegui l'API

Eseguire l'API per assicurarsi che sia in esecuzione senza errori usando il comando dotnet run. Se si intende usare il protocollo HTTPS anche durante i test, è necessario considerare attendibile . Il certificato di sviluppo di NET.

  1. Avviare l'applicazione digitando quanto segue nel terminale:

    dotnet run
    
  2. Un output simile al seguente deve essere visualizzato nel terminale, che conferma che l'applicazione è in esecuzione http://localhost:{port} e in ascolto delle richieste.

    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 pagina Web http://localhost:{host} visualizza un output simile all'immagine seguente. Ciò è dovuto al fatto che l'API viene chiamata senza autenticazione. Per effettuare una chiamata autorizzata, vedere Passaggi successivi per istruzioni su come accedere a un'API Web protetta.

Screenshot che mostra l'errore 401 all'avvio della pagina Web.

Per un esempio completo di questo codice API, vedere il file di esempi di.

Passaggi successivi