Protección de una API web de back-end para aplicaciones multiinquilino

Código de ejemplo

La aplicación Tailspin Surveys utiliza una API web de back-end web para administrar las operaciones CRUD en las encuestas. Por ejemplo, cuando un usuario hace clic en "My Surveys" (Mis encuestas), la aplicación web envía una solicitud HTTP a la API web:

GET /users/{userId}/surveys

La API web devuelve un objeto JSON:

{
  "Published":[],
  "Own":[
    {"Id":1,"Title":"Survey 1"},
    {"Id":3,"Title":"Survey 3"},
    ],
  "Contribute": [{"Id":8,"Title":"My survey"}]
}

La API web no permite solicitudes anónimas, por lo que la aplicación web debe autenticarse utilizando tokens de portador de OAuth 2.

Nota

Este es un escenario de servidor a servidor. La aplicación no realiza llamadas de AJAX a la API desde el explorador del cliente.

Puede basarse fundamentalmente en dos métodos:

  • Identidad de usuario delegado. La aplicación web se autentica con la identidad del usuario.
  • Identidad de aplicación. La aplicación web se autentica con su identificador de cliente, mediante el flujo de credenciales del cliente de OAuth 2.

La aplicación Tailspin implementa la identidad de usuario delegado. Estas son la diferencias principales:

Identidad de usuario delegada:

  • El token de portador enviado a la API web contiene la identidad del usuario.
  • La API web toma decisiones de autorización basadas en la identidad del usuario.
  • La aplicación web necesita administrar errores 403 (Forbidden) desde la API web en caso de que el usuario no esté autorizado para realizar una acción.
  • Normalmente, la aplicación web sigue tomando algunas decisiones de autorización que afectan a la IU, como el hecho de mostrar u ocultar los elementos de la IU.
  • La API web la pueden usar por clientes que no sean de confianza, como una aplicación JavaScript o una aplicación cliente nativa.

Identidad de aplicación:

  • La API web no obtiene información sobre el usuario.
  • La API web no emite ninguna autorización basándose en la identidad del usuario. Todas las decisiones de autorización son adoptadas por la aplicación web.
  • La API web no puede utilizarse por un cliente que no sea de confianza (aplicación JavaScript o aplicación cliente nativa).
  • Este planteamiento puede ser un poco más fácil de implementar, porque no hay ninguna lógica de autorización en la API web.

En cualquier planteamiento, la aplicación web debe obtener un token de acceso, que es la credencial necesaria para llamar a la API web.

  • En el caso de la identidad de usuario delegado, el token tiene que provenir de un proveedor de identidades (IDP), como Azure Active Directory, que puede emitir un token en nombre del usuario.
  • En el caso de las credenciales de cliente, una aplicación puede obtener el token del IDP u hospedar su propio servidor de tokens (No escriba un servidor de tokens desde cero; utilice una plataforma probada como IdentityServer4). Si la autenticación se realiza con Azure AD, se recomienda encarecidamente obtener el token de acceso de Azure AD, incluso con el flujo de credenciales de cliente.

En el resto de este artículo se supone que la aplicación se autentica con Azure AD.

Getting the access token

Diagrama que muestra la aplicación web que solicita un token de acceso desde Azure AD y lo envía a la API web.

Registro de la API web en Azure AD

Para que Azure AD emita un token de portador para la API web, necesita hacer algunos ajustes en Azure AD.

  1. Registre la API web en Azure AD.

  2. Agregue el identificador de cliente de la aplicación web al manifiesto de la aplicación de API web, en la propiedad knownClientApplications . Consulte el archivo Léame de GitHub para obtener más información.

  3. Conceda a la aplicación web permiso para llamar a la API web. En Azure Portal, puede establecer dos tipos de permisos: "Permisos de aplicación" para la identidad de aplicación (flujo de credenciales de cliente) o "Permisos delegados" para la identidad de usuario delegada.

    Delegated permissions

    Captura de pantalla de Azure Portal que muestra los permisos de la aplicación y los permisos delegados.

Obtención de un token de acceso

Antes de llamar a la API web, la aplicación web obtiene un token de acceso de Azure AD. En una aplicación de .NET, use la biblioteca de autenticación de Microsoft para .NET (MSAL.NET). Agregue .EnableTokenAcquisitionToCallDownstreamApi() en el archivo Startup.cs de la aplicación.

Después de adquirir un token, MSAL lo almacena en caché. Por lo tanto, también tendrá que elegir una implementación de caché de tokens, que se incluye en MSAL. En este ejemplo se usa la caché distribuida. Para obtener información detallada, consulte Almacenamiento en caché de tokens.

Uso del token de acceso para llamar a la API web

Una vez que tenga el token, llame a una API web de nivel inferior. Ese proceso se describe en Llamada a una API web de nivel inferior con la clase auxiliar.

Autenticación en la API web

La API web tiene que autenticar el token de portador. En ASP.NET Core, se puede usar el paquete Microsoft.AspNet.Authentication.JwtBearer. Este paquete proporciona software intermedio que permite a la aplicación recibir los tokens de portador de OpenID Connect.

Registre el software intermedio en la clase Startup de la API web.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(jtwOptions =>
        {
            jtwOptions.Events = new SurveysJwtBearerEvents(loggerFactory.CreateLogger<SurveysJwtBearerEvents>()); 
        },
        msIdentityOptions => {
            Configuration.GetSection("AzureAd").Bind(msIdentityOptions);
        });

Events es una clase que deriva de JwtBearerEvents.

Validación del emisor

Valide el emisor del token en el evento JwtBearerEvents.TokenValidated. El emisor se envía en la notificación "iss".

En la aplicación Surveys, la API web no controla el registro de inquilinos. Por lo tanto, solo comprueba si el emisor ya está en la base de datos de la aplicación. Si no es así, se produce una excepción, lo que provoca un error de autenticación.

public override async Task TokenValidated(TokenValidatedContext context)
{
    var principal = context.Ticket.Principal;
    var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
    var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
    var issuerValue = principal.GetIssuerValue();
    var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue);

    if (tenant == null)
    {
        // The caller was not from a trusted issuer. Throw to block the authentication flow.
        throw new SecurityTokenValidationException();
    }

    var identity = principal.Identities.First();

    // Add new claim for survey_userid
    var registeredUser = await userManager.FindByObjectIdentifier(principal.GetObjectIdentifierValue());
    identity.AddClaim(new Claim(SurveyClaimTypes.SurveyUserIdClaimType, registeredUser.Id.ToString()));
    identity.AddClaim(new Claim(SurveyClaimTypes.SurveyTenantIdClaimType, registeredUser.TenantId.ToString()));

    // Add new claim for Email
    var email = principal.FindFirst(ClaimTypes.Upn)?.Value;
    if (!string.IsNullOrWhiteSpace(email))
    {
        identity.AddClaim(new Claim(ClaimTypes.Email, email));
    }
}

Como se muestra en este ejemplo, también se puede usar el evento TokenValidated para modificar las notificaciones. Recuerde que las notificaciones proceden directamente de Azure AD. Si la aplicación web modifica las notificaciones que obtiene, dichos cambios no se mostrarán en el token de portador que recibe la API web. Para más información, consulte Transformaciones de notificaciones.

Authorization

Para un análisis general sobre la autorización, consulte Autorización basada en roles y recursos.

El middleware JwtBearer controla las respuestas de la autorización. Por ejemplo, para restringir una acción del controlador a los usuarios autenticados, utilice el atributo [Authorize] y especifique JwtBearerDefaults.AuthenticationScheme como esquema de autenticación:

[Authorize(ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Esto devuelve un código de estado 401 si el usuario no está autenticado.

Para restringir una acción del controlador por directiva de autorización, especifique el nombre de la directiva en el atributo [Authorize] :

[Authorize(Policy = PolicyNames.RequireSurveyCreator)]

Esto devuelve un código de estado 401 si el usuario no está autenticado, y 403 si el usuario está autenticado pero no autorizado. Registre la directiva en el inicio:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy(PolicyNames.RequireSurveyCreator,
            policy =>
            {
                policy.AddRequirements(new SurveyCreatorRequirement());
                policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
                policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            });
        options.AddPolicy(PolicyNames.RequireSurveyAdmin,
            policy =>
            {
                policy.AddRequirements(new SurveyAdminRequirement());
                policy.RequireAuthenticatedUser(); // Adds DenyAnonymousAuthorizationRequirement
                policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            });
    });

    // ...
}

Protección de los secretos de aplicación

Es habitual tener opciones de configuración de la aplicación que son confidenciales y deben protegerse, por ejemplo:

  • Cadenas de conexión de base de datos
  • Contraseñas
  • Claves de cifrado

Como procedimiento recomendado de seguridad, no debe almacenar nunca estos secretos en el control de código fuente. Es muy fácil que se filtren, incluso si el repositorio de código fuente es privado. Y no se trata solo de proteger los secretos frente al público general. En proyectos de mayor tamaño, es posible que desee restringir qué desarrolladores y operadores pueden tener acceso a los secretos de producción. (La configuración de los entornos de prueba o desarrollo son diferentes).

Una opción más segura consiste en almacenar estos secretos en Azure Key Vault. Key Vault es un servicio hospedado en la nube para administrar claves criptográficas y otros secretos. Este artículo muestra cómo usar Key Vault para almacenar las opciones de configuración de la aplicación.

En la aplicación Tailspin Surveys, los siguientes parámetros de configuración son secretos:

  • La cadena de conexión de base de datos.
  • La cadena de conexión de Redis.
  • El secreto de cliente de la aplicación web.

La aplicación Surveys carga las opciones de configuración desde las siguientes ubicaciones:

  • El archivo appsettings.json
  • El almacén de secretos del usuario (solo entorno de desarrollo; para pruebas)
  • El entorno de hospedaje (configuración de la aplicación en aplicaciones web de Azure)
  • Key Vault (cuando está habilitado)

Cada uno de estos invalida al anterior, por lo que la configuración almacenada en Key Vault tiene prioridad.

Nota

De forma predeterminada, el proveedor de configuración de Key Vault está deshabilitado. No es necesario para ejecutar la aplicación localmente. Lo habilitará en una implementación de producción.

En el inicio, la aplicación lee la configuración de cada proveedor de configuración registrado y los usa para rellenar un objeto de opciones fuertemente tipado. Para obtener más información, consulte Uso de opciones y objetos de configuración.

Next