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

Web-API's kunnen informatie bevatten waarvoor gebruikersverificatie en autorisatie is vereist. Toepassingen kunnen gebruikmaken van gedelegeerde toegang, handelend namens een aangemelde gebruiker, of alleen-app-toegang, die alleen fungeert als de eigen identiteit van de toepassing bij het aanroepen van beveiligde web-API's.

In deze zelfstudie bouwen we een web-API die zowel gedelegeerde machtigingen (bereiken) als toepassingsmachtigingen (app-rollen) publiceert. Client-apps, zoals web-apps, die tokens verkrijgen namens een aangemelde gebruiker, gebruiken de gedelegeerde machtigingen. Client-apps, zoals daemon-apps die tokens voor zichzelf verkrijgen, gebruiken de toepassingsmachtigingen.

In deze zelfstudie leert u het volgende:

Uw web-API configureren tp gebruik de app-registratiegegevens Configureer uw web-API voor het gebruik van gedelegeerde 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 database in het geheugen. Het is niet ontworpen voor productiegebruik.
  • Microsoft.Identity.Webvereenvoudigt het toevoegen van verificatie- en autorisatieondersteuning aan web-apps en web-API's die integreren met de Microsoft identity platform.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Details van app-registratie configureren

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

{
    "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 door Enter_the_Tenant_Subdomain_Here het subdomein van uw map (tenant).

App-rol en -bereik toevoegen

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

We geven deze machtigingen op in het bestand appsettings.json . In deze zelfstudie hebben we vier machtigingen geregistreerd. ToDoList.ReadWrite en ToDoList.Read als de 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

Een verificatieschema krijgt de naam wanneer de verificatieservice wordt geconfigureerd tijdens de verificatie. In dit artikel gebruiken we het JWT Bearer-verificatieschema. Voeg de volgende code toe aan het bestand Programs.cs 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 en 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 voor een gegevensmodel coördineert. Deze klasse wordt gemaakt op basis 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 en 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 bestand Program.cs in de hoofdmap van uw app en voeg vervolgens de volgende code toe aan het bestand. Met deze code wordt een DbContext subklasse met de naam ToDoContext als een scoped service geregistreerd in de ASP.NET Core toepassingsserviceprovider (ook wel de container voor afhankelijkheidsinjectie genoemd). 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. Doorgaans acties maken, lezen, bijwerken en verwijderen (CRUD). In deze zelfstudie maken we slechts twee actie-items. Een actie-item alle lezen en een actie-item maken om te laten zien hoe u uw eindpunten kunt beveiligen.

  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 de volgende boiler plate-code 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 ligt hier niet op het bouwen van de API, maar op het beveiligen ervan.

  1. Importeer de benodigde pakketten. Het pakket Microsoft.Identity.Web is een MSAL-wrapper waarmee we eenvoudig verificatielogica kunnen verwerken, bijvoorbeeld door tokenvalidatie af te handelen. 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 het aanroepen van deze API met behulp van gedelegeerde machtigingen namens de gebruiker of toepassingsmachtigingen waarbij de client aanroept als zichzelf en niet namens de gebruiker, is het belangrijk om te weten of de aanroep door de app zelf wordt uitgevoerd. 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 of een app - en gebruikerstoken is. U wordt aangeraden de idtyp optionele claim in te schakelen.

    Als de idtyp claim niet is ingeschakeld, kunt u de roles claims en scp gebruiken om te bepalen of het toegangstoken een app-token of een app - en gebruikerstoken is. Een toegangstoken dat is uitgegeven door Microsoft Entra Externe ID heeft ten minste een 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 uitgegeven 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 hebben, moeten niet 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 het de app is die de aanvraag zelf doet 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 eindpunt ALLES OPHALEN en het POST-eindpunt. Doe dit met behulp van 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 zonder fouten goed wordt uitgevoerd met behulp van de opdracht dotnet run. Als u zelfs tijdens het testen het HTTPS-protocol wilt gebruiken, moet u vertrouwen. Het ontwikkelingscertificaat van NET.

Zie het bestand met voorbeelden voor een volledig voorbeeld van deze API-code.

Volgende stappen