Delen via


Zelfstudie: Een ASP.NET Core-web-API beveiligen die is geregistreerd in een externe tenant

Deze reeks zelfstudies laat zien hoe u een geregistreerde web-API in de externe tenant beveiligt. In deze zelfstudie bouwt u een ASP.NET Core-web-API die zowel gedelegeerde machtigingen (bereiken) als toepassingsmachtigingen (app-rollen) publiceert.

In deze zelfstudie;

  • Uw web-API configureren voor het gebruik van de app-registratiegegevens
  • Uw web-API configureren voor het gebruik van gedelegeerde machtigingen en toepassingsmachtigingen die zijn geregistreerd in de app-registratie
  • Uw web-API-eindpunten beveiligen

Vereisten

Een ASP.NET Core-web-API maken

  1. Open de terminal en navigeer naar de map waarin u het project wilt opslaan.

  2. Voer de volgende opdrachten uit:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. Wanneer u in een dialoogvenster wordt gevraagd of u vereiste assets aan het project wilt toevoegen, selecteert u Ja.

Pakketten installeren

Installeer de volgende pakketten:

  • Microsoft.EntityFrameworkCore.InMemory waarmee Entity Framework Core kan worden gebruikt met een in-memory database. Het is niet ontworpen voor productiegebruik.
  • Microsoft.Identity.Web vereenvoudigt het toevoegen van verificatie- en autorisatieondersteuning aan web-apps en web-API's die worden geïntegreerd met het Microsoft Identity Platform.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Details van app-registratie configureren

Open het appsettings.json-bestand in uw app-map en voeg de app-registratiegegevens toe die u hebt vastgelegd nadat u de web-API hebt geregistreerd.

{
    "AzureAd": {
        "Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here",
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

Vervang de volgende tijdelijke aanduidingen zoals weergegeven:

  • Vervang door Enter_the_Application_Id_Here de id van uw toepassing (client).
  • Vervang door Enter_the_Tenant_Id_Here uw map-id (tenant).
  • Vervang Enter_the_Tenant_Subdomain_Here door het subdomein Directory (tenant).

Aangepast URL-domein gebruiken (optioneel)

Gebruik een aangepast domein om de verificatie-URL volledig te merken. Vanuit gebruikersperspectief blijven gebruikers in uw domein tijdens het verificatieproces in plaats van omgeleid naar ciamlogin.com domeinnaam.

Volg deze stappen om een aangepast domein te gebruiken:

  1. Gebruik de stappen in Aangepaste URL-domeinen inschakelen voor apps in externe tenants om aangepast URL-domein in te schakelen voor uw externe tenant.

  2. Open appsettings.json bestand:

    1. Werk de waarde van de Instance eigenschap bij naar https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Vervang Enter_the_Custom_Domain_Here door uw aangepaste URL-domein en Enter_the_Tenant_ID_Here door uw tenant-id. Als u uw tenant-id niet hebt, leest u de details van uw tenant.
    2. Voeg knownAuthorities de eigenschap toe met een waarde [Enter_the_Custom_Domain_Here].

Nadat u de wijzigingen in uw appsettings.json-bestand hebt aangebracht, als uw aangepaste URL-domein is login.contoso.com en uw tenant-id aaaabbbb-0000-cccc-1111-dddd2222eeeee is, moet uw bestand er ongeveer uitzien als het volgende fragment:

{
    "AzureAd": {
        "Instance": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here",
        "KnownAuthorities": ["login.contoso.com"]
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

App-rol en -bereik toevoegen

Alle API's moeten minimaal één bereik, ook wel gedelegeerde machtiging genoemd, publiceren voor de client-apps om een toegangstoken voor een gebruiker te verkrijgen. API's moeten ook minimaal één app-rol publiceren voor toepassingen, ook wel toepassingsmachtigingen genoemd, zodat de client-apps een toegangstoken kunnen verkrijgen als zichzelf, dat wil gezegd wanneer ze zich niet aanmelden bij een gebruiker.

We geven deze machtigingen op in het appsettings.json-bestand . In deze zelfstudie hebben we vier machtigingen geregistreerd. ToDoList.ReadWrite en ToDoList.Read als gedelegeerde machtigingen en ToDoList.ReadWrite.All en ToDoList.Read.All als de toepassingsmachtigingen.

{
  "AzureAd": {
    "Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
    "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": "*"
}

Verificatieschema toevoegen

Er wordt een verificatieschema genoemd wanneer de verificatieservice wordt geconfigureerd tijdens de verificatie. In dit artikel gebruiken we het JWT Bearer-verificatieschema. Voeg de volgende code toe aan het Programs.cs-bestand om een verificatieschema toe te voegen.

// Add the following to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

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

Uw modellen maken

Maak een map met de naam Modellen in de hoofdmap van uw project. Navigeer naar de map en maak een bestand met de naam ToDo.cs voeg vervolgens de volgende code toe. Met deze code maakt u een model met de naam ToDo.

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

Een databasecontext toevoegen

De databasecontext is de belangrijkste klasse die de Functionaliteit van Entity Framework coördineert voor een gegevensmodel. Deze klasse wordt gemaakt door afgeleid van de klasse Microsoft.EntityFrameworkCore.DbContext . In deze zelfstudie gebruiken we een in-memory database voor testdoeleinden.

  1. Maak een map met de naam DbContext in de hoofdmap van het project.

  2. Navigeer naar die map en maak een bestand met de naam ToDoContext.cs voeg vervolgens de volgende inhoud toe aan dat bestand:

    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. Open het Program.cs-bestand in de hoofdmap van uw app en voeg vervolgens de volgende code toe aan het bestand. Met deze code wordt een DbContext subklasse geregistreerd die wordt aangeroepen ToDoContext als een scoped service in de ASP.NET Core-toepassingsserviceprovider (ook wel bekend als de container voor afhankelijkheidsinjectie). De context is geconfigureerd voor het gebruik van de in-memory database.

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

Controllers toevoegen

In de meeste gevallen heeft een controller meer dan één actie. Normaal gesproken maken, lezen, bijwerken en verwijderen (CRUD) acties. In deze zelfstudie maken we slechts twee actie-items. Een actie-item lezen en een actie-item maken om te laten zien hoe u uw eindpunten beveiligt.

  1. Navigeer naar de map Controllers in de hoofdmap van uw project.

  2. Maak een bestand met de naam ToDoListController.cs in deze map. Open het bestand en voeg vervolgens de volgende boilerplaatcode toe:

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

Code toevoegen aan de controller

In deze sectie voegen we code toe aan de tijdelijke aanduidingen die we hebben gemaakt. De focus hier ligt niet op het bouwen van de API, maar op het beveiligen ervan.

  1. Importeer de benodigde pakketten. Het Microsoft.Identity.Web-pakket is een MSAL-wrapper waarmee we verificatielogica eenvoudig kunnen verwerken, bijvoorbeeld door tokenvalidatie te verwerken. Om ervoor te zorgen dat voor onze eindpunten autorisatie is vereist, gebruiken we het ingebouwde Microsoft.AspNetCore.Authorization-pakket .

  2. Omdat we machtigingen hebben verleend voor deze API die moet worden aangeroepen met gedelegeerde machtigingen namens de gebruiker of toepassingsmachtigingen waarbij de client zichzelf aanroept en niet namens de gebruiker, is het belangrijk om te weten of de aanroep door de app zelf wordt gedaan. De eenvoudigste manier om dit te doen, is de claims om te bepalen of het toegangstoken de idtyp optionele claim bevat. Deze idtyp claim is de eenvoudigste manier voor de API om te bepalen of een token een app-token is of een app + gebruikerstoken. U wordt aangeraden de idtyp optionele claim in te schakelen.

    Als de idtyp claim niet is ingeschakeld, kunt u de roles en scp claims gebruiken om te bepalen of het toegangstoken een app-token of een app + gebruikerstoken is. Een toegangstoken dat is uitgegeven door Microsoft Entra Externe ID heeft ten minste één van de twee claims. Toegangstokens die zijn uitgegeven aan een gebruiker, hebben de scp claim. Toegangstokens die zijn uitgegeven aan een toepassing, hebben de roles claim. Toegangstokens die beide claims bevatten, worden alleen verleend aan gebruikers, waarbij de scp claim de gedelegeerde machtigingen aanwijst, terwijl de roles claim de rol van de gebruiker aanwijst. Toegangstokens die geen van beide moeten worden gehonoreerd.

    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. Voeg een helperfunctie toe die bepaalt of de aanvraag die wordt gedaan voldoende machtigingen bevat om de beoogde actie uit te voeren. Controleer of de app de aanvraag zelf indient of dat de app de aanroep doet namens een gebruiker die eigenaar is van de opgegeven resource door de gebruikers-id te valideren.

    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. Sluit uw machtigingsdefinities aan om routes te beveiligen. Beveilig uw API door het [Authorize] kenmerk toe te voegen aan de controllerklasse. Dit zorgt ervoor dat de controlleracties alleen kunnen worden aangeroepen als de API wordt aangeroepen met een geautoriseerde identiteit. De machtigingsdefinities definiëren welke soorten machtigingen nodig zijn om deze acties uit te voeren.

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

    Voeg machtigingen toe aan het GET-eindpunt en het POST-eindpunt. Gebruik hiervoor de methode RequiredScopeOrAppPermission die deel uitmaakt van de naamruimte Microsoft.Identity.Web.Resource . Vervolgens geeft u bereiken en machtigingen door aan deze methode via de kenmerken RequiredScopesConfigurationKey en 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);
    }
    

Uw API uitvoeren

Voer uw API uit om ervoor te zorgen dat deze goed wordt uitgevoerd zonder fouten met behulp van de opdracht dotnet run. Als u het HTTPS-protocol zelfs tijdens het testen wilt gebruiken, moet u vertrouwen. Het ontwikkelingscertificaat van NET.

Zie het voorbeeldbestand voor een volledig voorbeeld van deze API-code.

Volgende stap