Compartir a través de


Tutorial: Compilación y protección de una API web de ASP.NET Core con la plataforma de identidad de Microsoft

Se aplica a:Círculo verde con un símbolo de marca de verificación blanca. inquilinos de personal Círculo verde con un símbolo de marca de verificación blanca. inquilinos externos (más información)

En esta serie de tutoriales se muestra cómo proteger una API web de ASP.NET Core con la plataforma de identidad de Microsoft para limitar el acceso solo a usuarios autorizados y aplicaciones cliente. La API web que compila usa permisos delegados (ámbitos) y permisos de aplicación (roles de aplicación).

En este tutorial, harás lo siguiente:

  • Compilación de una API web de ASP.NET Core
  • Configuración de la API web para usar los detalles de registro de aplicaciones de Microsoft Entra
  • Protección de los puntos de conexión de API web
  • Ejecución de la API web para asegurarse de que escucha solicitudes HTTP

Prerrequisitos

Creación de un nuevo proyecto de API web de ASP.NET Core

Para crear un proyecto de API web de ASP.NET core mínimo, siga estos pasos:

  1. Abra el terminal en Visual Studio Code o en cualquier otro editor de código y vaya al directorio donde desea crear el proyecto.

  2. Ejecute los comandos siguientes en la CLI de .NET o en cualquier otra herramienta de línea de comandos.

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. Seleccione cuando un cuadro de diálogo le pregunte si desea confiar en los autores.

  4. Seleccione Cuando un cuadro de diálogo le pregunte si desea agregar los recursos necesarios al proyecto.

Instalación de paquetes necesarios

Para compilar, proteger y probar la API web de ASP.NET Core, debe instalar los siguientes paquetes:

  • Microsoft.EntityFrameworkCore.InMemory: un paquete que permite usar Entity Framework Core con una base de datos en memoria. Resulta útil para realizar pruebas, pero no está diseñada para su uso en producción.
  • Microsoft.Identity.Web : un conjunto de bibliotecas de ASP.NET Core que simplifican la adición de compatibilidad de autenticación y autorización a aplicaciones web y API web que se integran con la plataforma de identidad de Microsoft.

Para instalar el paquete, use:

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

Configuración de los detalles de registro de una aplicación

Abra el archivo appsettings.json en la carpeta de la aplicación y agregue los detalles de registro de la aplicación que registró después de registrar la API web.

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

Reemplace los siguientes marcadores de posición como se indica:

  • Reemplace Enter_the_Application_Id_Here por id. de la aplicación (cliente).
  • Reemplace Enter_the_Tenant_Id_Here con su ID de Directorio (tenant).
  • Reemplace por Enter_the_Authority_URL_Here la dirección URL de la entidad, como se explica en la sección siguiente.

URL de autoridad de tu aplicación

La dirección URL de autoridad especifica el directorio desde el que la Biblioteca de autenticación de Microsoft (MSAL) puede solicitar tokens. Puede compilarlo de forma diferente tanto en los inquilinos de recursos como en los externos, como se muestra a continuación:

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

Uso del dominio de dirección URL personalizado (opcional)

Los dominios de dirección URL personalizados no se admiten en los inquilinos de recursos de trabajo.

Agregar permisos

Todas las API deben publicar al menos un ámbito, también denominado permiso delegado, para que las aplicaciones cliente obtengan un token de acceso para un usuario. Las APIs también deben publicar un mínimo de un rol de aplicación, también denominado permisos de aplicación, para que las aplicaciones cliente obtengan un token de acceso por sí mismas, es decir, cuando no están iniciando sesión de un usuario.

Especificamos estos permisos en el archivo appsettings.json. En este tutorial, registró los siguientes permisos delegados y de aplicación:

  • Permisos delegados:ToDoList.Read y ToDoList.ReadWrite.
  • Permisos de aplicaciones:ToDoList.Read.All y ToDoList.ReadWrite.All.

Cuando una aplicación cliente o usuario llama a la API web, solo los clientes con estos ámbitos o permisos obtienen autorización para acceder al punto de conexión protegido.

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

Implementación de la autenticación y autorización en la API

Para configurar la autenticación y la autorización, abra el program.cs archivo y reemplace su contenido por los siguientes fragmentos de código:

Adición de un esquema de autenticación

En esta API, usamos el esquema de Bearer JSON Web Token (JWT) como mecanismo de autenticación predeterminado. Use el AddAuthentication método para registrar el esquema de portador 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);

Creación del modelo de la aplicación

En la carpeta raíz del proyecto, cree una carpeta denominada Models. Vaya a la carpeta Models y cree un archivo denominado ToDo.cs y agregue el código siguiente.

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

El código anterior crea un modelo denominado ToDo. Este modelo representa los datos que administra la aplicación.

Incorporación de un contexto de base de datos

A continuación, definimos una clase de contexto de base de datos, que coordina la funcionalidad de Entity Framework para un modelo de datos. Esta clase hereda de la clase Microsoft.EntityFrameworkCore.DbContext que administra las interacciones entre la aplicación y la base de datos. Para agregar el contexto de la base de datos, siga estos pasos:

  1. Cree una carpeta denominada DbContext en la carpeta raíz del proyecto.

  2. Vaya a la carpeta DbContext y cree un archivo denominado ToDoContext.cs y agregue el código siguiente:

    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. Abra el archivo Program.cs en la carpeta raíz del proyecto y actualícelo con el código siguiente:

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

En el fragmento de código anterior, registramos el contexto de base de datos como un servicio con ámbito en el proveedor de servicios de aplicaciones de ASP.NET Core (también conocido como contenedor de inserción de dependencias). También configurará la clase ToDoContext para usar una base de datos en memoria para la API de lista de todo.

Configuración de un controlador

Los controladores suelen implementar acciones Crear, Leer, Actualizar y Eliminar (CRUD) para administrar recursos. Dado que este tutorial se centra más en la protección de los puntos de conexión de API, solo se implementan dos elementos de acción en el controlador. Acción Leer todo para recuperar todos los elementos To-Do y una acción Crear para agregar un nuevo elemento To-Do. Siga estos pasos para agregar un controlador al proyecto:

  1. Vaya a la carpeta raíz del proyecto y cree una carpeta denominada Controllers.

  2. Crea un archivo denominado ToDoListController.cs dentro de la carpeta Controllers y agrega el siguiente código predefinido:

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

Agregar código al controlador

Esta sección explica cómo agregar código al controlador estructurado en la sección anterior. El enfoque aquí es proteger la API, no desarrollarla.

  1. Importe los paquetes necesarios: El Microsoft.Identity.Web paquete es un contenedor alrededor de MSAL.NET que nos ayuda a controlar fácilmente la lógica de autenticación, como controlar la validación de tokens. Para asegurarse de que nuestros puntos de conexión requieren autorización, usamos el paquete integrado Microsoft.AspNetCore.Authorization .

  2. Dado que se han concedido permisos para llamar a esta API mediante permisos delegados en nombre del usuario o permisos de aplicación en los que el cliente llama como sí mismo y no en nombre del usuario, es importante saber si la aplicación realiza la llamada en su propio nombre. La manera más fácil de hacerlo es encontrar si el token de acceso contiene la notificación opcional idtyp. Esta notificación idtyp es la forma más sencilla para que una API determine si un token es un token de aplicación o un token de usuario + aplicación. Recomendamos habilitar la reclamación opcional idtyp.

    Si la notificación idtyp no está habilitada, puede usar las notificaciones roles y scp para determinar si el token de acceso es un token de aplicación o un token de usuario + aplicación. Un token de acceso emitido por Microsoft Entra ID tiene al menos uno de los dos atributos. Los tokens de acceso emitidos para un usuario tienen la notificación scp. Los tokens de acceso emitidos para una aplicación tienen la notificación roles. Los tokens de acceso que contienen ambas notificaciones solo se emiten a los usuarios, donde la notificación scp designa los permisos delegados, mientras que la notificación roles designa el rol del usuario. No se respetan los tokens de acceso que no tienen ninguna.

    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. Agregue una función auxiliar que determine si la solicitud que se realiza contiene suficientes permisos para llevar a cabo la acción prevista. Compruebe si es la aplicación la que realiza la solicitud en su propio nombre o si la aplicación está realizando la llamada en nombre de un usuario propietario del recurso en cuestión mediante la validación del identificador de usuario.

    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. Conecte las definiciones de permisos para proteger las rutas. Proteja la API agregando el atributo [Authorize] a la clase de controlador. Se garantiza así que solo se pueda llamar a las acciones de controlador si se llama a la API con una identidad autorizada. Las definiciones de permisos definen qué tipos de permisos son necesarios para realizar estas acciones.

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

    Agregue permisos a los puntos de conexión GET y POST. Para ello, use el método RequiredScopeOrAppPermission que forma parte del espacio de nombres Microsoft.Identity.Web.Resource. A continuación, pase los ámbitos y permisos a este método a través de los atributos RequiredScopesConfigurationKey y 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);
    }
    

Configuración del middleware de API para usar el controlador

A continuación, configuramos la aplicación para que reconozca y use controladores para controlar las solicitudes HTTP. Abra el archivo y agregue el program.cs código siguiente para registrar los servicios de controlador en el contenedor de inserción de dependencias.


builder.Services.AddControllers();

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

app.Run();

En el fragmento de código anterior, el AddControllers() método prepara la aplicación para usar controladores mediante el registro de los servicios necesarios mientras asigna MapControllers() las rutas del controlador para controlar las solicitudes HTTP entrantes.

Ejecutar la API

Ejecute la API para asegurarse de que se está ejecutando sin errores mediante el comando dotnet run. Si piensa usar el protocolo HTTPS incluso durante las pruebas, debe confiar en el certificado de desarrollo de .NET.

  1. Inicie la aplicación escribiendo lo siguiente en el terminal:

    dotnet run
    
  2. Se debe mostrar una salida similar a la siguiente en el terminal, lo que confirma que la aplicación se está ejecutando en http://localhost:{port} y escuchando peticiones.

    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 página web http://localhost:{host} muestra una salida similar a la siguiente imagen. Esto se debe a que se llama a la API sin autenticación. Para realizar una llamada autorizada, consulte Pasos siguientes para obtener instrucciones sobre cómo acceder a una API web protegida.

Captura de pantalla que muestra el error 401 cuando se inicia la página web.

Para obtener un ejemplo completo de este código de API, consulte el archivo de ejemplos.

Pasos siguientes