Implemente la autorización en las API web con Microsoft. Identity.Web

En este artículo, implementará la autorización en APIs web de ASP.NET Core mediante Microsoft.Identity.Web. Validará los ámbitos (permisos delegados) y los permisos de aplicación (permisos de aplicación) para controlar el acceso a los recursos protegidos. Los ejemplos usan Microsoft Entra ID como proveedor de identidades.

Descripción de los conceptos de autorización

En esta sección se describen las diferencias clave entre la autenticación y la autorización, y se describe qué Microsoft. Identity.Web valida en tokens de acceso.

Autenticación frente a autorización

Concepto propósito Resultado
Autenticación Comprobación de la identidad 401 No autorizado si se produce un error
Autorización Comprobar los permisos 403 Prohibido si no es suficiente

¿Qué se valida?

Cuando una API web recibe un token de acceso, Microsoft. Identity.Web valida:

  1. Firma de token : ¿Procede de una entidad de confianza?
  2. Audiencia de tokens : ¿Está pensada para esta API?
  3. Expiración del token : ¿Sigue siendo válido?
  4. Ámbitos/roles - ¿tiene la aplicación cliente y el asunto (usuario) los permisos adecuados?

Esta guía se centra en el número 4: validación de ámbitos y permisos de aplicación.

Ámbitos (permisos delegados)

Los ámbitos se aplican cuando un usuario delega permiso a una aplicación para que actúe en su nombre (por ejemplo, una API web llamada en nombre de un usuario que ha iniciado sesión).

Detalle Importancia
Reclamación de token scp o scope (aplicación cliente); roles (usuario)
Valores de ejemplo "access_as_user", , "User.Read", "Files.ReadWrite"

Permisos de aplicación

Los permisos de aplicación se aplican cuando una aplicación llama a la API web como tal sin contexto de usuario, como un servicio daemon o un servicio en segundo plano mediante credenciales de cliente.

Detalle Importancia
Declaración de token roles
Valores de ejemplo "Mail.Read.All", "User.Read.All"

Validación de ámbitos con RequiredScope

El RequiredScope atributo comprueba que el token de acceso contiene al menos uno de los ámbitos especificados. Use este atributo cuando la API solo atiende solicitudes delegadas por el usuario.

Configuración de la validación de ámbito

Siga estos pasos para habilitar la validación del ámbito en la API.

1. Habilite la autorización en la API:

Agregue servicios de autenticación y autorización a la canalización de la aplicación:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. Proteja controladores o acciones:

Aplique los [Authorize] atributos y [RequiredScope] al controlador o a las acciones individuales:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

Aplicar patrones de ámbito

Elija el patrón que mejor se adapte a cómo administrar ámbitos en la aplicación.

Patrón 1: ámbitos codificados de forma fija

Use este patrón cuando los ámbitos sean fijos y conocidos en tiempo de desarrollo.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

Para aceptar cualquiera de varios ámbitos, enumere como parámetros:

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

Patrón 2: Alcances de la configuración

Use este patrón cuando los ámbitos se deben configurar por entorno. Defina los ámbitos en el archivo de configuración:

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

Haga referencia a la clave de configuración en el controlador:

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

Este enfoque le permite cambiar los ámbitos sin volver a compilar.

Patrón 3: ámbitos de nivel de acción

Use este patrón cuando las diferentes acciones requieran permisos diferentes. Aplicar [RequiredScope] a métodos de acción individuales:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

Descripción del flujo de validación

Cuando llega una solicitud, el middleware lo procesa en el orden siguiente:

  1. ASP.NET Core middleware de autenticación valida el token
  2. RequiredScope verificaciones de atributos para la reclamación scp o scope
  3. Si el token contiene al menos un ámbito coincidente, la solicitud continúa.
  4. Si no se encuentra ningún ámbito coincidente, la API devuelve una respuesta 403 Prohibido.

En el ejemplo siguiente se muestra una respuesta de error típica:

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

Validación de permisos de aplicación con RequiredScopeOrAppPermission

El RequiredScopeOrAppPermission atributo valida los ámbitos (delegados) o los permisos de aplicación (aplicación). Use este atributo cuando la API sirva tanto aplicaciones delegadas por el usuario como aplicaciones de demonio o servicio desde el mismo punto de conexión.

Si la API solo atiende solicitudes delegadas por el usuario, use RequiredScope en su lugar.

Configurar el ámbito o la validación de permisos de la aplicación

Aplique el atributo para aceptar cualquier tipo de token:

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

Configuración de permisos de aplicación desde la configuración

Almacene ámbitos y permisos de aplicación en la configuración para cambiarlos sin volver a compilarlos.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

Haga referencia a las claves de configuración del controlador:

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

Comparar las diferencias en las reclamaciones de token

En la tabla siguiente se muestra cómo difieren las reclamaciones entre los tokens delegados por el usuario y solo para aplicaciones.

Tipo de token Reclamación Valor de ejemplo
Delegado por el usuario scp o scope "access_as_user User.Read"
Solo aplicación roles ["TodoList.ReadWrite.All"]

En el ejemplo siguiente se muestra un token delegado por el usuario:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

En el ejemplo siguiente se muestra un token de solo aplicación:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

Creación de directivas de autorización

Para escenarios de autorización complejos, use las políticas de autorización de ASP.NET Core. Las políticas permiten centralizar reglas, combinar varios requisitos y escribir lógica de autorización comprobable.

Ventajas Description
Lógica centralizada Definición de reglas de autorización una vez, reutilización en todas partes
Composable Combinar varios requisitos (ámbitos + declaraciones + lógica personalizada)
Testable Facilitar la prueba unitaria de la lógica de autorización
Flexible Requisitos personalizados más allá de la validación del ámbito

Patrón 1: Definición de una directiva con RequireScope

Defina directivas con nombre que requieran ámbitos específicos y, a continuación, haga referencia a ellas en los controladores:

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

Aplique las directivas a las acciones del controlador:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

Patrón 2: Definir una directiva con ScopeAuthorizationRequirement

Use ScopeAuthorizationRequirement para requisitos de ámbito más explícitos:

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

Patrón 3: Establecimiento de una directiva predeterminada

Establezca automáticamente una directiva predeterminada que se aplique a todos los [Authorize] atributos:

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

Ahora todos los [Authorize] atributos requieren el access_as_user ámbito:

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

Patrón 4: Combinar varios requisitos

Combine los requisitos de ámbito, rol y autenticación en una sola directiva:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

Patrón 5: Creación de una directiva a partir de la configuración

Cargar ámbitos desde la configuración para mantener las políticas específicas para el entorno.

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

Filtrado de solicitudes por inquilino

Restrinja el acceso de API a tokens de clientes de Microsoft Entra específicos. Esto resulta útil cuando la API de múltiples inquilinos solo debe aceptar solicitudes de inquilinos aprobados de clientes.

Restricción del acceso a los inquilinos permitidos

Defina una directiva que compruebe la reclamación de ID de inquilino en una lista de autorizaciones:

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

Configura el filtrado de inquilinos desde los ajustes

Almacene los identificadores de inquilino permitidos en la configuración para administrarlos sin cambios en el código.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

Lea la lista de inquilinos y cree la directiva en el inicio:

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

Combinación de ámbitos con filtrado de arrendatarios

Cree una directiva que requiera un ámbito válido y un inquilino aprobado:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

Seguimiento de los procedimientos recomendados

Aplique estas recomendaciones para crear lógica de autorización segura y fácil de mantener.

Qué hacer

1. Siempre emparejar [Authorize] con la validación del alcance:

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. Use la configuración para ámbitos específicos del entorno:

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. Aplicar privilegios mínimos:

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. Usar directivas para la autorización compleja:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. Habilitar respuestas de error detalladas en el desarrollo:

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

Cosas que no debes hacer

1. No omita [Authorize] al usar RequiredScope:

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. No codifique de forma dura los identificadores de inquilino en producción:

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. No confunda ámbitos con roles:

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. No exponga información de ámbito confidencial en los mensajes de error de producción:

Configure los niveles de registro y el control de errores adecuados para entornos de producción.


Solución de problemas de autorización

Use las instrucciones siguientes para diagnosticar problemas comunes de autorización.

403 Prohibido: ámbito que falta

Error: La API devuelve 403 incluso con un token válido.

Diagnóstico:

  1. Descodifique el token en jwt.ms.
  2. Compruebe el scp reclamo o scope.
  3. Compruebe que el valor coincide con el RequiredScope atributo.

Solution:

  • Asegúrese de que la aplicación cliente solicita el ámbito correcto al adquirir el token.
  • Compruebe que el ámbito se expone en el registro de la aplicación de API en Microsoft Entra.
  • Conceda el consentimiento del administrador si es necesario.

RequiredScope no funciona

Síntoma: Parece que se omite el atributo .

Comprobación:

  1. ¿Ha agregado el [Authorize] atributo?
  2. ¿Se llama a app.UseAuthorization() después de app.UseAuthentication()?
  3. ¿Está services.AddAuthorization() registrado?

No se encontró la clave de configuración

Error: Se produce un error en la validación del ámbito de forma silenciosa.

Comprobación:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

Asegúrese de que la ruta de acceso de configuración coincida exactamente.