API web protegida: Comprobación de ámbitos y roles de aplicación

En este artículo se describe cómo se puede agregar autorización a la API web. Esta protección garantiza que solo llamen a la API:

  • Aplicaciones en nombre de usuarios que tienen los ámbitos y roles correctos.
  • Aplicaciones de demonio que tienen los roles de aplicación correctos.

Los fragmentos de código de este artículo se han extraído de los ejemplos de código siguientes en GitHub:

Para proteger una API web de ASP.NET o ASP.NET Core, debe agregar el atributo [Authorize] en uno de los siguientes elementos:

  • El propio controlador, si quiere que todas las acciones del controlador estén protegidas.
  • La acción de controlador individual para la API.
    [Authorize]
    public class TodoListController : Controller
    {
     // ...
    }

Aun así, esta protección no es suficiente. Solo garantiza que ASP.NET y ASP.NET Core validen el token. La API debe verificar que el token usado para llamar a la API se haya solicitado con las notificaciones esperadas. Estas notificaciones requieren una verificación en particular:

  • Los ámbitos, si se llama a la API en nombre de un usuario.
  • Los roles de aplicación, si se puede llamar a la API desde una aplicación de demonio.

Verificación de los ámbitos de las API a las que se llama en nombre de los usuarios

Si una aplicación cliente llama a la API en nombre de un usuario, la API debe solicitar un token de portador con ámbitos específicos para la API. Para más información, consulte Configuración del código | Token de portador.

En ASP.NET Core, puede usar Microsoft.Identity.Web para comprobar los ámbitos de cada acción del controlador. También puede comprobarlos en el nivel del controlador o para toda la aplicación.

Verificación de los ámbitos en cada acción de controlador

Puede comprobar los ámbitos de la acción del controlador mediante el atributo [RequiredScope]. Este atributo tiene varias invalidaciones. Una que toma directamente los ámbitos necesarios y otra que toma una clave para la configuración.

Comprobación de los ámbitos de una acción del controlador con ámbitos codificados de forma rígida

El siguiente fragmento de código muestra el uso del atributo [RequiredScope] con ámbitos codificados de forma rígida.

using Microsoft.Identity.Web

[Authorize]
public class TodoListController : Controller
{
    /// <summary>
    /// The web API will accept only tokens that have the `access_as_user` scope for
    /// this API.
    /// </summary>
    const string scopeRequiredByApi = "access_as_user";

    // GET: api/values
    [HttpGet]
    [RequiredScope(scopeRequiredByApi)]
    public IEnumerable<TodoItem> Get()
    {
        // Do the work and return the result.
        // ...
    }
 // ...
}
Comprobación de los ámbitos de una acción del controlador con ámbitos definidos en la configuración

También puede declarar estos ámbitos necesarios en la configuración y hacer referencia a la clave de configuración:

Por ejemplo, si en appsettings.json tiene la siguiente configuración:

{
 "AzureAd" : {
   // more settings
   "Scopes" : "access_as_user access_as_admin"
  }
}

Haga referencia a ella en el atributo [RequiredScope]:

using Microsoft.Identity.Web

[Authorize]
public class TodoListController : Controller
{
    // GET: api/values
    [HttpGet]
    [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
    public IEnumerable<TodoItem> Get()
    {
        // Do the work and return the result.
        // ...
    }
 // ...
}
Comprobación condicional de ámbitos

Hay casos en los que se quiere comprobar los ámbitos de forma condicional. Puede hacerlo mediante el método de extensión VerifyUserHasAnyAcceptedScope de HttpContext.

using Microsoft.Identity.Web

[Authorize]
public class TodoListController : Controller
{
    /// <summary>
    /// The web API will accept only tokens 1) for users, 2) that have the `access_as_user` scope for
    /// this API.
    /// </summary>
    static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

    // GET: api/values
    [HttpGet]
    public IEnumerable<TodoItem> Get()
    {
         HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
        // Do the work and return the result.
        // ...
    }
 // ...
}

Comprobación de los ámbitos en el nivel del controlador

También puede comprobar los ámbitos de todo el controlador.

Comprobación de los ámbitos de un controlador con ámbitos codificados de forma rígida

El siguiente fragmento de código muestra el uso del atributo [RequiredScope] con ámbitos codificados de forma rígida en el controlador. Para usar RequiredScopeAttribute, deberá hacer lo siguiente:

using Microsoft.Identity.Web

[Authorize]
[RequiredScope(scopeRequiredByApi)]
public class TodoListController : Controller
{
    /// <summary>
    /// The web API will accept only tokens 1) for users, 2) that have the `access_as_user` scope for
    /// this API.
    /// </summary>
    static readonly string[] scopeRequiredByApi = new string[] { "access_as_user" };

    // GET: api/values
    [HttpGet]
    public IEnumerable<TodoItem> Get()
    {
        // Do the work and return the result.
        // ...
    }
 // ...
}
Comprobación de los ámbitos de un controlador con ámbitos definidos en la configuración

Al igual que en una acción, también puede declarar estos ámbitos necesarios en la configuración y hacer referencia a la clave de configuración:

using Microsoft.Identity.Web

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : Controller
{
    // GET: api/values
    [HttpGet]
    public IEnumerable<TodoItem> Get()
    {
        // Do the work and return the result.
        // ...
    }
 // ...
}

Verificación de los ámbitos más globalmente

La definición de ámbitos pormenorizados para la API web y la verificación de los ámbitos en cada acción del controlador es el enfoque recomendado. Sin embargo, también es posible comprobar los ámbitos en el nivel de la aplicación o de un controlador. Para obtener más información, consulte Autorización basada en notificaciones en la documentación de ASP.NET Core.

¿Qué se comprueba?

El atributo [RequiredScope] y el método VerifyUserHasAnyAcceptedScope hacen algo similar a los pasos siguientes:

  • Verificar que hay una notificación denominada http://schemas.microsoft.com/identity/claims/scope o scp.
  • Verificar que la notificación tiene un valor que contiene el ámbito que espera la API.

Verificación de los roles de aplicación de las API a las que llaman aplicaciones de demonio

Si una aplicación de demonio llama a la API web, la aplicación debe solicitar un permiso de aplicación a la API web. Como se muestra en Exposición de los permisos de aplicación (roles de aplicación), la API expone estos permisos. Un ejemplo es el rol de aplicación access_as_application.

Ahora debe hacer que la API verifique que el token recibido contiene la notificación roles y que esta notificación tiene el valor esperado. El código de verificación es similar al código que verifica los permisos delegados, con la diferencia de que la acción del controlador prueba los roles en lugar de los ámbitos:

El siguiente fragmento de código muestra cómo comprobar el rol de la aplicación.

using Microsoft.Identity.Web

[Authorize]
public class TodoListController : ApiController
{
    public IEnumerable<TodoItem> Get()
    {
        HttpContext.ValidateAppRole("access_as_application");
        // ...
    }

En su lugar, puede usar los atributos de [Authorize(Roles = "access_as_application")] en el controlador o una acción (o una página de Razor).

[Authorize(Roles = "access_as_application")]
MyController : ApiController
{
    // ...
}

La autorización basada en roles ASP.NET Core enumera varios enfoques para implementar este tipo de autorización. Los desarrolladores pueden elegir aquella que responda a sus escenarios.

Para ver ejemplos funcionales, consulte el tutorial incremental de la aplicación web sobre la autorización por roles y grupos.

Verificación de roles de aplicación en las API invocadas en nombre de los usuarios

Los usuarios también pueden usar notificaciones de roles en patrones de asignación de usuarios, tal como se muestra en Procedimiento para agregar roles de aplicación en la aplicación y recibirlos en el token. Si los roles se pueden asignar en ambos casos, comprobar los roles permitirá a las aplicaciones iniciar sesión como usuarios y a los usuarios iniciar sesión como aplicaciones. Le recomendamos que declare roles diferentes para los usuarios y las aplicaciones a fin de evitar esta confusión.

Si ha definido roles de aplicación con un usuario o grupo, la notificación de roles también se puede comprobar en la API, junto con los ámbitos. La lógica de comprobación de los roles de aplicación en este escenario sigue siendo la misma que si las aplicaciones de demonio llaman a la API, ya que no hay diferenciación en la notificación de rol para el usuario o grupo y la aplicación.

Aceptación de tokens solo de aplicación si la API web debe invocarse únicamente mediante aplicaciones de demonio

Si quiere que únicamente las aplicaciones de demonio llamen a la API web, al validar el rol de aplicación, agregue una condición de que el token es solo de aplicación.

string oid = ClaimsPrincipal.Current.FindFirst("oid")?.Value;
string sub = ClaimsPrincipal.Current.FindFirst("sub")?.Value;
bool isAppOnly = oid != null && sub != null && oid == sub;

Si aplica la condición inversa, permite que llamen a la API solo las aplicaciones que inician la sesión de un usuario.

Uso de la autorización basada en ACL

Como alternativa a la autorización basada en roles de aplicación, puede proteger la API web con un patrón de autorización basado en una lista de control de acceso (ACL) para controlar los tokens sin la notificación roles.

Si usa Microsoft.Identity.Web en ASP.NET Core, deberá declarar que usa la autorización basada en ACL; de lo contrario, Microsoft.Identity.Web producirá una excepción cuando no haya roles ni ámbitos en las notificaciones proporcionadas:

System.UnauthorizedAccessException: IDW10201: Neither scope or roles claim was found in the bearer token.

Para evitar esta excepción, establezca la propiedad de configuración AllowWebApiToBeAuthorizedByACL en true en appsettings.json o mediante programación.

{
 "AzureAD"
 {
  // other properties
  "AllowWebApiToBeAuthorizedByACL" : true,
  // other properties
 }
}

Si establece AllowWebApiToBeAuthorizedByACL en true, es su responsabilidad garantizar el mecanismo de la lista de control de acceso.

Pasos siguientes

  • Para más información, cree una aplicación web de ASP.NET Core que inicie sesión a los usuarios en las siguientes series de tutoriales de varias partes

  • Exploración de ejemplos de API web de la plataforma de identidad de Microsoft