Autenticación y autorización en ASP.NET Web API

Por Rick Anderson

Ha creado una API web, pero ahora quiere controlar el acceso a ella. En esta serie de artículos, veremos algunas opciones para proteger una API web de usuarios no autorizados. Esta serie abarcará tanto la autenticación como la autorización.

  • La autenticación consiste en conocer la identidad del usuario. Por ejemplo, Alice inicia sesión con su nombre de usuario y contraseña, y el servidor usa la contraseña para autenticar a Alice.
  • La autorización decide si un usuario puede realizar una acción. Por ejemplo, Alice tiene permiso para obtener un recurso, pero no para crear un recurso.

En el primer artículo de la serie se proporciona información general sobre la autenticación y autorización en ASP.NET Web API. Otros temas describen escenarios de autenticación comunes para la API web.

Nota:

Gracias a las personas que revisaron esta serie y proporcionaron comentarios valiosos: Rick Anderson, Levi Broderick, Barry Dorrans, Tom Dykstra, Hongmei Ge, David Matson, Daniel Roth y Tim Teebken.

Autenticación

La API web supone que la autenticación se produce en el host. Para el hospedaje web, el host es IIS, que usa módulos HTTP para la autenticación. Puede configurar el proyecto para que use cualquiera de los módulos de autenticación integrados en IIS o ASP.NET, o escribir su propio módulo HTTP para realizar la autenticación personalizada.

Cuando el host autentica al usuario, crea una entidad de seguridad, que es un objeto IPrincipal que representa el contexto de seguridad en el que se ejecuta el código. El host adjunta la entidad de seguridad al subproceso actual estableciendo Thread.CurrentPrincipal. La entidad de seguridad contiene un objeto Identity asociado que contiene información sobre el usuario. Si el usuario está autenticado, la propiedad Identity.IsAuthenticated devuelve true. En el caso de las solicitudes anónimas, IsAuthenticated devuelve false. Para obtener más información sobre las entidades de seguridad, consulte Seguridad basada en roles.

Controladores de mensajes HTTP para la autenticación

En lugar de usar el host para la autenticación, puede colocar la lógica de autenticación en un controlador de mensajes HTTP. En ese caso, el controlador de mensajes examina la solicitud HTTP y establece la entidad de seguridad.

¿Cuándo debe usar controladores de mensajes para la autenticación? Estos son algunos inconvenientes:

  • Un módulo HTTP ve todas las solicitudes que pasan por la canalización de ASP.NET. Un controlador de mensajes solo ve solicitudes que se enrutan a la API web.
  • Puede establecer controladores de mensajes por ruta, lo que le permite aplicar un esquema de autenticación a una ruta específica.
  • Los módulos HTTP son específicos de IIS. Los controladores de mensajes son independientes del host, por lo que se pueden usar tanto con hospedaje web como con autohospedaje.
  • Los módulos HTTP participan en el registro, la auditoría, etc. de IIS.
  • Los módulos HTTP se ejecutan anteriormente en la canalización. Si controla la autenticación en un controlador de mensajes, la entidad de seguridad no se establece hasta que se ejecuta el controlador. Además, la entidad de seguridad vuelve a la entidad de seguridad anterior cuando la respuesta deja el controlador de mensajes.

Por lo general, si no es necesario admitir el autohospedaje, un módulo HTTP es una mejor opción. Si necesita admitir el autohospedaje, considere la posibilidad de usar un controlador de mensajes.

Establecimiento de la entidad de seguridad

Si la aplicación realiza cualquier lógica de autenticación personalizada, debe establecer la entidad de seguridad en dos lugares:

  • Thread.CurrentPrincipal. Esta propiedad es la manera estándar de establecer la entidad de seguridad del subproceso en .NET.
  • HttpContext.Current.User. Esta propiedad es específica de ASP.NET.

El código siguiente muestra cómo establecer la entidad de seguridad:

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

Para el hospedaje web, debe establecer la entidad de seguridad en ambos lugares; de lo contrario, el contexto de seguridad puede ser incoherente. Sin embargo, para el autohospedaje, HttpContext.Current es null. Para asegurarse de que el código es independiente del host, compruebe si hay valores null antes de asignarlo a HttpContext.Current, como se muestra.

Authorization

La autorización se produce más adelante en la canalización, más cerca del controlador. Esto le permite tomar decisiones más pormenorizadas al conceder acceso a los recursos.

  • Los filtros de autorización se ejecutan antes de la acción del controlador. Si la solicitud no está autorizada, el filtro devuelve una respuesta de error y la acción no se invoca.
  • Dentro de una acción del controlador, puede obtener la entidad de seguridad actual de la propiedad ApiController.User. Por ejemplo, podría filtrar una lista de recursos en función del nombre de usuario, devolviendo solo los recursos que pertenecen a ese usuario.

Diagram of the authentication and authorization pipeline.

Uso del atributo [Authorize]

La API web proporciona un filtro de autorización integrado: AuthorizeAttribute. Este filtro comprueba si el usuario está autenticado. Si no es así, devuelve el código de estado HTTP 401 (no autorizado), sin invocar la acción.

Puede aplicar el filtro globalmente, en el nivel de controlador o en el nivel de acciones individuales.

Globalmente: para restringir el acceso para cada controlador Web API, agregue el filtro AuthorizeAttribute a la lista de filtros globales:

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Controlador: para restringir el acceso a un controlador específico, agregue el filtro como atributo al controlador:

// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

Acción: para restringir el acceso a acciones específicas, agregue el atributo al método de acción:

public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

Como alternativa, puede restringir el controlador y, a continuación, permitir el acceso anónimo a acciones específicas mediante el atributo [AllowAnonymous]. En el siguiente ejemplo, se restringe el método Post, pero el método Get permite el acceso anónimo.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}

En los ejemplos anteriores, el filtro permite que cualquier usuario autenticado acceda a los métodos restringidos; solo se mantienen fuera los usuarios anónimos. También puede limitar el acceso a usuarios específicos o a usuarios en roles específicos:

// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

Nota:

El filtro AuthorizeAttribute para controladores de API web se encuentra en el espacio de nombres System.Web.Http. Hay un filtro similar para los controladores MVC en el espacio de nombres System.Web.Mvc, que no es compatible con controladores de API web.

Filtros de autorización personalizados

Para escribir un filtro de autorización personalizado, derive de uno de estos tipos:

  • AuthorizeAttribute. Extienda esta clase para realizar la lógica de autorización en función del usuario actual y de los roles del usuario.
  • AuthorizationFilterAttribute. Extienda esta clase para realizar lógica de autorización sincrónica que no se basa necesariamente en el usuario o rol actual.
  • IAuthorizationFilter. Implemente esta interfaz para realizar lógica de autorización asincrónica; por ejemplo, si la lógica de autorización realiza llamadas asincrónicas de E/S o de red. (Si la lógica de autorización está limitada por la CPU, es más sencillo derivar de AuthorizationFilterAttribute, porque entonces no necesita escribir un método asíncrono).

En el siguiente diagrama se muestra la jerarquía de clases de la clase AuthorizeAttribute.

Diagram of the class hierarchy for the Authorize Attribute class.

Diagrama de la jerarquía de clases para la clase Authorize Attribute. Authorize Attribute está en la parte inferior, con una flecha que apunta a Authorization Filter Attribute (Atributo de filtro de autorización) y una flecha que apunta a I Authorization Filter (Filtro de autorización I) en la parte superior.

Autorización dentro de una acción del controlador

En algunos casos, puede permitir que una solicitud continúe, pero cambie el comportamiento en función de la entidad de seguridad. Por ejemplo, la información que devuelve podría cambiar en función del rol del usuario. Dentro de un método del controlador, puede obtener la entidad de seguridad actual de la propiedad ApiController.User.

public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}