Självstudie: Skydda ett ASP.NET webb-API som är registrerat i en kundklientorganisation

Webb-API:er kan innehålla information som kräver användarautentisering och auktorisering. Program kan använda delegerad åtkomst, agera på uppdrag av en inloggad användare eller endast appåtkomst, som endast fungerar som programmets egen identitet när du anropar skyddade webb-API:er.

I den här självstudien skapar vi ett webb-API som publicerar både delegerade behörigheter (omfång) och programbehörigheter (approller). Klientappar som webbappar som hämtar token för en inloggad användare använder de delegerade behörigheterna. Klientappar som daemonappar som hämtar token själva använder programbehörigheterna.

I den här guiden får du lära dig att:

Konfigurera din webb-API tp använda dess appregistreringsinformation Konfigurera ditt webb-API för att använda delegerade och programbehörigheter som registrerats i appregistreringen Skydda dina webb-API-slutpunkter

Förutsättningar

Skapa ett ASP.NET Core webb-API

  1. Öppna terminalen och navigera sedan till mappen där du vill att projektet ska finnas.

  2. Kör följande kommandon:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. När en dialogruta frågar om du vill lägga till nödvändiga tillgångar i projektet väljer du Ja.

Installera paket

Installera följande paket:

  • Microsoft.EntityFrameworkCore.InMemory som gör att Entity Framework Core kan användas med en minnesintern databas. Den är inte avsedd för produktionsanvändning.
  • Microsoft.Identity.Webförenklar tillägg av stöd för autentisering och auktorisering för webbappar och webb-API:er som integreras med Microsofts identitetsplattform.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Konfigurera appregistreringsinformation

Öppna filen appsettings.json i appmappen och lägg till den appregistreringsinformation som du registrerade när du registrerade webb-API:et.

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

Ersätt följande platshållare enligt följande:

  • Ersätt Enter_the_Application_Id_Here med ditt program-ID (klient).
  • Ersätt Enter_the_Tenant_Id_Here med ditt katalog-ID (klientorganisation).
  • Ersätt Enter_the_Tenant_Subdomain_Here med underdomänen Katalog (klientorganisation).

Lägg till approll och omfång

Alla API:er måste publicera minst ett omfång, även kallat delegerad behörighet, för att klientapparna ska kunna hämta en åtkomsttoken för en användare. API:er bör också publicera minst en approll för program, även kallat programbehörighet, för att klientapparna ska kunna hämta en åtkomsttoken som sig själva, det vill s.v.s. när de inte loggar in en användare.

Vi anger dessa behörigheter i filen appsettings.json . I den här självstudien har vi registrerat fyra behörigheter. ToDoList.ReadWrite och ToDoList.Read som delegerade behörigheter och ToDoList.ReadWrite.All och ToDoList.Read.All som programbehörigheter.

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

Lägg till autentiseringsschema

Ett autentiseringsschema namnges när autentiseringstjänsten konfigureras under autentiseringen. I den här artikeln använder vi JWT-ägarautentiseringsschemat. Lägg till följande kod i filen Programs.cs för att lägga till ett autentiseringsschema.

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

Skapa dina modeller

Skapa en mapp med namnet Modeller i rotmappen för projektet. Navigera till mappen och skapa en fil med namnet ToDo.cs och lägg sedan till följande kod. Den här koden skapar en modell med namnet 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;
}

Lägga till en databaskontext

Databaskontexten är huvudklassen som samordnar Entity Framework-funktioner för en datamodell. Den här klassen skapas genom att härledas från klassen Microsoft.EntityFrameworkCore.DbContext . I den här självstudien använder vi en minnesintern databas i testsyfte.

  1. Skapa en mapp med namnet DbContext i projektets rotmapp.

  2. Navigera till mappen och skapa en fil med namnet ToDoContext.cs och lägg sedan till följande innehåll i filen:

    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. Öppna filen Program.cs i appens rotmapp och lägg sedan till följande kod i filen. Den här koden registrerar en DbContext underklass som kallas ToDoContext för en begränsad tjänst i ASP.NET Core programtjänstleverantör (kallas även för containern för beroendeinmatning). Kontexten är konfigurerad för att använda den minnesinterna databasen.

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

Lägga till kontrollanter

I de flesta fall skulle en kontrollant ha mer än en åtgärd. Vanligtvis skapar, läser, uppdaterar och tar bort (CRUD) åtgärder. I den här självstudien skapar vi bara två åtgärdsobjekt. Ett läsa alla åtgärdsobjekt och ett skapa åtgärdsobjekt för att visa hur du skyddar dina slutpunkter.

  1. Gå till mappen Controllers i rotmappen för projektet.

  2. Skapa en fil med namnet ToDoListController.cs i den här mappen. Öppna filen och lägg sedan till följande pannplåtskod:

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

Lägga till kod i kontrollanten

I det här avsnittet lägger vi till kod i platshållarna som vi skapade. Fokus här är inte på att skapa API:et, utan snarare på att skydda det.

  1. Importera nödvändiga paket. Microsoft.Identity.Web-paketet är en MSAL-omslutning som hjälper oss att enkelt hantera autentiseringslogik, till exempel genom att hantera tokenvalidering. För att säkerställa att våra slutpunkter kräver auktorisering använder vi det inbyggda Microsoft.AspNetCore.Authorization-paketet .

  2. Eftersom vi har beviljat behörigheter för att det här API:et ska anropas antingen med delegerade behörigheter för användarens eller programmets behörigheter där klienten anropar som sig själv och inte för användarens räkning, är det viktigt att veta om anropet görs av appen för egen räkning. Det enklaste sättet att göra detta är anspråken för att ta reda på om åtkomsttoken innehåller det valfria anspråket idtyp . Det här idtyp anspråket är det enklaste sättet för API:et att avgöra om en token är en apptoken eller en app + användartoken. Vi rekommenderar att du aktiverar det valfria anspråket idtyp .

    Om anspråket idtyp inte är aktiverat kan du använda anspråken roles och scp för att avgöra om åtkomsttoken är en apptoken eller en app + användartoken. En åtkomsttoken som utfärdats av Microsoft Entra External ID har minst ett av de två anspråken. Åtkomsttoken som utfärdas till en användare har anspråket scp . Åtkomsttoken som utfärdas till ett program har anspråket roles . Åtkomsttoken som innehåller båda anspråken utfärdas endast till användare, där anspråket scp anger de delegerade behörigheterna, medan anspråket roles anger användarens roll. Åtkomsttoken som inte har någondera ska respekteras.

    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. Lägg till en hjälpfunktion som avgör om begäran som görs innehåller tillräckligt med behörigheter för att utföra den avsedda åtgärden. Kontrollera om det är appen som gör begäran för egen räkning eller om appen anropar för en användare som äger den angivna resursen genom att verifiera användar-ID:t.

    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. Anslut dina behörighetsdefinitioner för att skydda vägar. Skydda ditt API genom att lägga till [Authorize] attributet i kontrollantklassen. Detta säkerställer att kontrollantåtgärderna endast kan anropas om API:et anropas med en auktoriserad identitet. Behörighetsdefinitionerna definierar vilka typer av behörigheter som krävs för att utföra dessa åtgärder.

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

    Lägg till behörigheter till GET all-slutpunkten och POST-slutpunkten. Gör detta med hjälp av metoden RequiredScopeOrAppPermission som ingår i namnområdet Microsoft.Identity.Web.Resource . Sedan skickar du omfång och behörigheter till den här metoden via attributen RequiredScopesConfigurationKey och 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);
    }
    

Kör ditt API

Kör ditt API för att säkerställa att det körs bra utan fel med kommandot dotnet run. Om du tänker använda https-protokollet även under testningen måste du lita på . NET:s utvecklingscertifikat.

Ett fullständigt exempel på den här API-koden finns i exempelfilen.

Nästa steg