Uso de Identity para proteger un back-end de API web para SPA

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

ASP.NET Core Identity proporciona API que controlan la autenticación, la autorización y la administración de identidades. Las API permiten proteger los puntos de conexión de un back-end de API web con autenticación basada en cookie. Existe una opción basada en tokens para los clientes que no pueden usar cookie.

En este artículo se muestra cómo usar Identity para proteger un back-end de API web para SPA como aplicaciones de Angular, React y Vue. Las mismas API de back-end se pueden usar para proteger aplicaciones Blazor WebAssembly.

Requisitos previos

Los pasos que se muestran en este artículo agregan autenticación y autorización a una aplicación de API web principal de ASP.NET que:

  • Aún no está configurado para la autenticación.
  • Tiene como destino net8.0 o una versión posterior.
  • Puede ser una API mínima o una API basada en controladores.

Algunas de las instrucciones de prueba de este artículo usan la interfaz de usuario de Swagger que se incluye con la plantilla de proyecto. La interfaz de usuario de Swagger no es necesaria para usarIdentity con un back-end de API web.

Instalación de paquetes NuGet

Instale los siguientes paquetes NuGet:

Para obtener la forma más rápida de empezar, use la base de datos en memoria.

Cambie la base de datos más adelante a SQLite o SQL Server para guardar los datos de usuario entre sesiones al realizar pruebas o para su uso en producción. Esto presenta cierta complejidad en comparación con la memoria, ya que requiere que la base de datos se cree a través de migraciones, como se muestra en el EF Core tutorial de introducción.

Instale estos paquetes mediante el administrador de paquetes NuGet en Visual Studio o el comando de la CLI dotnet add package.

Cree una clave privada RSA IdentityDbContext.

Agregue una clase denominada ApplicationDbContext que herede de IdentityDbContext<TUser>:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

El código que se muestra proporciona un constructor especial que permite configurar la base de datos para distintos entornos.

Agregue una o varias de las siguientes directivas using según sea necesario al agregar el código que se muestra en estos pasos.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

Configuración del contexto EF Core

Como ya se ha indicado anteriormente, la manera más sencilla de empezar es usar la base de datos en memoria. Con la memoria, cada ejecución comienza con una base de datos nueva y no es necesario usar migraciones. Después de la llamada a WebApplication.CreateBuilder(args), agregue el código siguiente a fin de configurar Identity para que use una base de datos en memoria:

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

Para guardar los datos de usuario entre sesiones al realizar pruebas o para su uso en producción, cambie la base de datos más adelante a SQLite o SQL Server.

Adición de servicios Identity al contenedor

Después de la llamada a WebApplication.CreateBuilder(args), llame a AddAuthorization para agregar servicios al contenedor de inserción de dependencias (DI):

builder.Services.AddAuthorization();

Activación de las API de Identity

Después de la llamada a WebApplication.CreateBuilder(args), llame a AddIdentityApiEndpoints<TUser>(IServiceCollection) y a AddEntityFrameworkStores<TContext>(IdentityBuilder).

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

De forma predeterminada, se activan los tokens cookie y de propiedad. Cookies y tokens se emiten al iniciar sesión si el parámetro de cadena de consulta useCookies en el punto de conexión de inicio de sesión es true.

Asignación de rutas Identity

Después de la llamada a builder.Build(), llame a MapIdentityApi<TUser>(IEndpointRouteBuilder) para asignar los puntos de conexión Identity:

app.MapIdentityApi<IdentityUser>();

Protección de los puntos de conexión seleccionados

Para proteger un punto de conexión, use el método de extensión RequireAuthorization en la llamada Map{Method} que define la ruta. Por ejemplo:

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

El método RequireAuthorization también se puede usar para:

  • Proteger los puntos de conexión de la interfaz de usuario de Swagger, como se muestra en el siguiente ejemplo:

    app.MapSwagger().RequireAuthorization();
    
  • Proteger con una notificación o permiso específicos, como se muestra en el siguiente ejemplo:

    .RequireAuthorization("Admin");
    

En un proyecto de API web basado en controlador, proteja los puntos de conexión aplicando el atributo [Authorize] a un controlador o a una acción.

Prueba de la API

Una manera rápida de probar la autenticación es usar la base de datos en memoria y la interfaz de usuario de Swagger que se incluye con la plantilla de proyecto. Los pasos siguientes muestran cómo probar la API con la interfaz de usuario de Swagger. Asegúrese de que los puntos de conexión de la interfaz de usuario de Swagger no están protegidos.

Intento de acceso a un punto de conexión protegido

  • Ejecute la aplicación y vaya a la interfaz de usuario de Swagger.
  • Expanda un punto de conexión protegido, como /weatherforecast en un proyecto creado por la plantilla de API web.
  • Haga clic en Probar.
  • Seleccione Execute(Ejecutar). La respuesta es 401 - not authorized.

Registro de pruebas

  • Expanda /register y seleccione Pruébelo.

  • En la sección Parámetros de la interfaz de usuario, se muestra un cuerpo de solicitud de ejemplo:

    {
      "email": "string",
      "password": "string"
    }
    
  • Reemplace "string" por una dirección de correo electrónico y una contraseña válidas y, a continuación, seleccione Ejecutar.

    Para cumplir las reglas de validación de contraseña predeterminadas, la contraseña debe tener al menos seis caracteres y contener al menos uno de los siguientes caracteres:

    • Letra en mayúscula
    • Letra en minúscula
    • Dígito numérico
    • Carácter no alfanumérico

    Si escribe una dirección de correo electrónico no válida o una contraseña incorrecta, el resultado incluirá los errores de validación. Este es un ejemplo de un cuerpo de respuesta con errores de validación:

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    Los errores se devuelven en el formato ProblemDetails para que el cliente pueda analizarlos y mostrar los errores de validación según sea necesario.

    Un registro correcto da como resultado una respuesta 200 - OK.

Prueba del inicio de sesión

  • Expanda /login y seleccione Pruébelo. El cuerpo de la solicitud de ejemplo muestra dos parámetros adicionales:

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    Las propiedades JSON adicionales no son necesarias para este ejemplo y se pueden eliminar. Establezca useCookies en true.

  • Reemplace "string" por la dirección de correo electrónico y la contraseña que usó para registrarse y, a continuación, seleccione Ejecutar.

    Un inicio de sesión correcto da como resultado una respuesta 200 - OK con un elemento cookie el encabezado de respuesta.

Volver a probar el punto de conexión protegido

Después de un inicio de sesión correcto, vuelva a ejecutar el punto de conexión protegido. La autenticación cookie se envía automáticamente con la solicitud y el punto de conexión se autoriza. La autenticación basada en Cookie está integrada en el explorador y "simplemente funciona".

Prueba con clientes que no son de explorador

Es posible que algunos clientes web no incluyan cookie en el encabezado de forma predeterminada:

  • Si usa una herramienta para probar las API, es posible que tenga que habilitar cookie en la configuración.

  • La API de JavaScript fetch no incluye cookie de forma predeterminada. Habilítelos estableciendo credentials en el valor include de las opciones.

  • Un elemento de HttpClient ejecutándose en una aplicación Blazor WebAssembly necesita HttpRequestMessage para incluir credenciales, como el siguiente ejemplo:

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

Uso de la autenticación basada en tokens

Para los clientes que no admiten cookie, la API de inicio de sesión proporciona un parámetro para solicitar tokens. Se emite un token personalizado (uno que es propiedad de la plataforma de identidad de ASP.NET Core) que se puede usar para autenticar las solicitudes posteriores. El token se pasa en el encabezado Authorization como token de portador. También se proporciona un token de actualización. Este token permite a la aplicación solicitar un nuevo token cuando expira el antiguo sin forzar al usuario a iniciar sesión de nuevo.

Los tokens no son JStokens web ON estándar (JWT). El uso de tokens personalizados es intencional, ya que la API integrada Identity está pensada principalmente para escenarios simples. La opción de token no está pensada para ser un proveedor de servicios de identidad completo o un servidor de tokens, sino una alternativa a la opción cookie para los clientes que no pueden usar cookie.

Para usar la autenticación basada en tokens, establezca el parámetro de cadena de consulta useCookies en false al llamar al punto de conexión de /login. Los tokens usan el esquema de autenticación portador de. El uso del token devuelto desde la llamada a /login, las llamadas posteriores a los puntos de conexión protegidos deben agregar el encabezado Authorization: Bearer <token> donde <token> es el token de acceso. Para obtener más información, consulte Uso del punto de conexión de POST /login más adelante en este artículo.

Cerrar la sesión

Para proporcionar una manera de que el usuario cierre la sesión, defina un punto de conexión /logout como el siguiente ejemplo:

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

Proporcione un objeto JSON vacío ({}) en el cuerpo de la solicitud al llamar a este punto de conexión. El siguiente código es un ejemplo de una llamada al punto de conexión de cierre de sesión:

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

Puntos de conexión de MapIdentityApi<TUser>

La llamada a MapIdentityApi<TUser> agrega los siguientes puntos de conexión a la aplicación:

Uso del punto de conexión de POST /register

El cuerpo de la solicitud debe tener las propiedades Email y Password:

{
  "email": "string",
  "password": "string",
}

Para más información, vea:

Uso del punto de conexión de POST /login

En el cuerpo de la solicitud, Email y Password son necesarios. Si la autenticación en dos fases (2FA) está habilitada, se requiere TwoFactorCode o TwoFactorRecoveryCode. Si 2FA no está habilitado, omita twoFactorCode y twoFactorRecoveryCode. Para obtener más información, consulte Uso del punto de conexión de POST /manage/2fa más adelante en este artículo.

Este es un ejemplo de cuerpo de solicitud con 2FA sin habilitar:

{
  "email": "string",
  "password": "string"
}

Estos son ejemplos del cuerpo de la solicitud con 2FA habilitado:

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

El punto de conexión espera un parámetro de cadena de consulta:

  • useCookies - Se establece en true para la autenticación basada en cookie. Establezca false u omita para la autenticación basada en tokens.

Para obtener más información sobre la autenticación basada en cookie, consulte Probar el inicio de sesión, mencionado previamente en este artículo.

Autenticación basada en tokens

Si useCookies es false o se omite, se habilita la autenticación basada en token. El cuerpo de la respuesta incluye las siguientes propiedades:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Para obtener más información acerca de estas propiedades, consulte AccessTokenResponse.

Coloque el token de acceso en un encabezado para realizar solicitudes autenticadas, como se muestra en el siguiente ejemplo

Authorization: Bearer {access token}

Cuando el token de acceso esté a punto de expirar, llame al punto de conexión /refresh.

Uso del punto de conexión de POST /refresh

Solo para uso con autenticación basada en token. Obtiene un nuevo token de acceso sin forzar al usuario a iniciar sesión de nuevo. Llame a este punto de conexión cuando el token de acceso esté a punto de expirar.

El cuerpo de la solicitud contiene solo el RefreshToken. Este es un ejemplo de cuerpo de solicitud:

{
  "refreshToken": "string"
}

Si la llamada se realiza correctamente, el cuerpo de la respuesta es un nuevo AccessTokenResponse, como se muestra en el siguiente ejemplo:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

Uso del punto de conexión de GET /confirmEmail

Si Identity está configurado para la confirmación por correo electrónico, una llamada correcta al punto de conexión de /register envía un correo electrónico que contiene un vínculo al punto de conexión de /confirmEmail. El vínculo contiene los siguientes parámetros de cadena de consulta:

  • userId
  • code
  • changedEmail: solo se incluye si el usuario cambió la dirección de correo electrónico durante el registro.

De forma predeterminada, el asunto del correo electrónico es "Confirmación por correo electrónico" y el cuerpo del correo electrónico es similar al siguiente ejemplo:

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

Si la propiedad RequireConfirmedEmail está establecida en true, el usuario no puede iniciar sesión hasta que se confirme la dirección de correo electrónico haciendo clic en el vínculo del correo electrónico. El punto de conexión de /confirmEmail:

  • Confirma la dirección de correo electrónico y permite al usuario iniciar sesión.
  • Devuelve el texto "Gracias por confirmar el correo electrónico" en el cuerpo de la respuesta.

Para configurar Identity para la confirmación por correo electrónico, agregue código en Program.cs para establecer RequireConfirmedEmail en true y agregue una clase que implemente IEmailSender en el contenedor de inserción de dependencias. Por ejemplo:

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

En el ejemplo anterior, EmailSender es una clase que implementa IEmailSender. Para obtener más información, incluido un ejemplo de una clase que implementa IEmailSender, consulte Confirmación de la cuenta y recuperación de contraseñas en ASP.NET Core.

Uso del punto de conexión de POST /resendConfirmationEmail

Envía un correo electrónico solo si la dirección es válida para un usuario registrado.

El cuerpo de la solicitud contiene solo el Email. Este es un ejemplo de cuerpo de solicitud:

{
  "email": "string"
}

Para obtener más información, consulte Uso del punto de conexión de GET /confirmEmail mencionado previamente en este artículo.

Uso del punto de conexión de POST /forgotPassword

Genera un correo electrónico que contiene un código de restablecimiento de contraseña. Envíe ese código a /resetPassword con una nueva contraseña.

El cuerpo de la solicitud contiene solo el Email. Este es un ejemplo:

{
  "email": "string"
}

Para obtener información sobre cómo habilitar Identity para enviar correos electrónicos, consulte Uso del punto de conexión de GET /confirmEmail.

Uso del punto de conexión de POST /resetPassword

Llame a este punto de conexión después de obtener un código de restablecimiento llamando al punto de conexión de /forgotPassword.

El cuerpo de la solicitud requiere Email, ResetCode y NewPassword. Este es un ejemplo:

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

Uso del punto de conexión de POST /manage/2fa

Configura la autenticación en dos fases (2FA) para el usuario. Cuando se habilita 2FA, el inicio de sesión correcto requiere un código generado por una aplicación autenticadora además de la dirección de correo electrónico y la contraseña.

Habilitación de la autenticación en dos fases

Para habilitar 2FA para el usuario autenticado actualmente:

  • Llame al punto de conexión de /manage/2fa y envíe un JSobjeto ON ({}) vacío en el cuerpo de la solicitud.

  • El cuerpo de la respuesta proporciona el SharedKey junto con otras propiedades que no son necesarias en este momento. La clave compartida se usa para configurar la aplicación autenticadora. Ejemplo de cuerpo de respuesta:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • Use la clave compartida para obtener una contraseña de un solo uso (TOTP) basada en tiempo. Para obtener más información, consulte Habilitación de la generación de código QR para aplicaciones de autenticación TOTP en ASP.NET Core.

  • Llame al punto de conexión de /manage/2fa y envíe TOTP y "enable": true en el cuerpo de la solicitud. Por ejemplo:

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • El cuerpo de la respuesta confirma que IsTwoFactorEnabled es true y proporciona el RecoveryCodes. Los códigos de recuperación se usan para iniciar sesión cuando la aplicación autenticadora no está disponible. Ejemplo de cuerpo de respuesta después de habilitar correctamente 2FA:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

Inicio de sesión con 2FA

Llame al punto de conexión de /login y envíe la dirección de correo electrónico, la contraseña y TOTP en el cuerpo de la solicitud. Por ejemplo:

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

Si el usuario no tiene acceso a la aplicación autenticadora, inicie sesión llamando al punto de conexión de /login con uno de los códigos de recuperación proporcionados cuando se ha habilitado 2FA. El cuerpo de la solicitud se parece al siguiente ejemplo:

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

Restablecimiento los códigos de recuperación

Para obtener una nueva colección de códigos de recuperación, llame a este punto de conexión con ResetRecoveryCodes establecido en true. Este es un ejemplo de cuerpo de solicitud:

{
  "resetRecoveryCodes": true
}

Restablecimiento de la clave compartida

Para obtener una nueva clave compartida aleatoria, llame a este punto de conexión con ResetSharedKey establecido en true. Este es un ejemplo de cuerpo de solicitud:

{
  "resetSharedKey": true
}

Al restablecer la clave, se deshabilita automáticamente el requisito de inicio de sesión en dos fases para el usuario autenticado hasta que se vuelva a habilitar mediante una solicitud posterior.

Olvídate de la máquina

Para borrar la marca cookie "recuérdame" si está presente, llame a este punto de conexión con ForgetMachine establecido en true. Este es un ejemplo de cuerpo de solicitud:

{
  "forgetMachine": true
}

Este punto de conexión no afecta a la autenticación basada en tokens.

Uso del punto de conexión de GET /manage/info

Obtiene la dirección de correo electrónico y el estado de confirmación de correo electrónico del usuario que ha iniciado sesión. Las notificaciones se omiten de este punto de conexión por motivos de seguridad. Si se necesitan notificaciones, use las API del lado servidor para configurar un punto de conexión para las notificaciones. O bien, en lugar de compartir todas las notificaciones de los usuarios, proporcione un punto de conexión de validación que acepte una notificación y responda si el usuario lo tiene.

La solicitud no requiere ningún parámetro. El cuerpo de la respuesta incluye las propiedades Email y IsEmailConfirmed, como en el ejemplo siguiente:

{
  "email": "string",
  "isEmailConfirmed": true
}

Uso del punto de conexión de POST /manage/info

Actualiza la dirección de correo electrónico y la contraseña del usuario que ha iniciado sesión. Envíe NewEmail, NewPasswordy OldPassword en el cuerpo de la solicitud, como se muestra en el siguiente ejemplo:

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

Este es un ejemplo del cuerpo de la respuesta:

{
  "email": "string",
  "isEmailConfirmed": false
}

Consulte también

Para obtener más información, consulte los siguientes recursos:

La plantilla de ASP.NET Core ofrece autenticación en aplicaciones de página única (SPA) mediante la compatibilidad con la autorización de API. ASP.NET Core Identity para autenticar y almacenar usuarios se combina con DuendeIdentity Server para implementar OpenID Connect.

Importante

Duende Software puede requerir que pague una tarifa de licencia para el uso en producción de Duende Identity Server. Para más información, vea Migración de ASP.NET Core 5.0 a 6.0.

Se agregó un parámetro de autenticación a las plantillas de proyecto de Angular y React que es similar al parámetro de autenticación de las plantillas de proyecto de la Aplicación web (Modelo-Vista-Controlador) (MVC) y Aplicación web (Páginas de Razor). Los valores de parámetro permitidos son Ninguno e Individual. La plantilla de proyecto React.js y Redux no admite el parámetro de autenticación en este momento.

Creación de una aplicación con compatibilidad con la autorización de API

La autenticación y autorización de usuario se pueden usar para las SPA tanto de Angular como de React. Abra un shell de comandos y ejecute el comando siguiente:

Angular:

dotnet new angular -au Individual

React:

dotnet new react -au Individual

El comando anterior crea una aplicación de ASP.NET Core con un directorio ClientApp que contiene la SPA.

Descripción general de los componentes de ASP.NET Core de la aplicación

En las siguientes secciones se describen algunas adiciones al proyecto cuando se incluye compatibilidad con la autenticación.

Program.cs

Los siguientes ejemplos de código se basan en el paquete NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer. En los ejemplos se configura la autenticación y autorización de API mediante los métodos de extensión AddApiAuthorization y AddIdentityServerJwt. Los proyectos que usan plantillas de proyecto de SPA de React o Angular con autenticación incluyen una referencia a este paquete.

dotnet new angular -au Individual genera el siguiente archivo Program.cs:

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

El código anterior configura:

  • Identitycon la interfaz de usuario predeterminada:

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • IdentityServer con un método auxiliar de AddApiAuthorization adicional que configura convenciones de ASP.NET Core predeterminadas por encima de IdentityServer:

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • Autenticación con un método auxiliar AddIdentityServerJwt adicional que configura la aplicación para validar los tokens JWT generados por IdentityServer:

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • El middleware de autenticación es responsable de validar las credenciales de solicitud y de configurar el usuario en el contexto de la solicitud:

    app.UseAuthentication();
    
  • El middleware de IdentityServer expone los puntos de conexión de OpenID Connect:

    app.UseIdentityServer();
    

Azure App Service en Linux

Para implementaciones en Linux de Azure App Service, especifique el emisor explícitamente:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

En el código anterior, el marcador de posición {AUTHORITY} es el Authority que se va a usar al realizar llamadas a OpenID Connect.

Ejemplo:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Este método auxiliar configura IdentityServer para usar nuestra configuración admitida. IdentityServer es un marco eficaz y extensible que sirve para abordar los problemas de seguridad de las aplicaciones. Al mismo tiempo, expone complejidad innecesaria para los escenarios más comunes. En consecuencia, se le proporciona un conjunto de convenciones y opciones de configuración que consideramos un buen punto de partida. Cuando sus necesidades de autenticación cambien, tendrá a su disposición toda la eficacia de IdentityServer para personalizar la autenticación para adaptarse a sus necesidades.

AddIdentityServerJwt

Este método auxiliar configura un esquema de directiva para la aplicación como el controlador de autenticación predeterminado. La directiva está configurada para permitir que Identity controle todas las solicitudes enrutadas a cualquier subtrazado en el espacio de dirección URL de Identity "/Identity". JwtBearerHandler se encarga de todas las demás solicitudes. Además, este método registra un <<ApplicationName>>API recurso de API con IdentityServer con un ámbito predeterminado de <<ApplicationName>>API y configura el middleware de token de portador JWT para validar los tokens emitidos por IdentityServer para la aplicación.

WeatherForecastController

En el archivo, observe el atributo [Authorize] aplicado a la clase que indica que el usuario debe autorizarse en función de la directiva predeterminada para acceder al recurso. La directiva de autorización predeterminada se configura para usar el esquema de autenticación predeterminado, que se configura mediante AddIdentityServerJwt para el esquema de directiva mencionado anteriormente, lo que hace que el método auxiliar JwtBearerHandler configure el controlador predeterminado para las solicitudes a la aplicación.

ApplicationDbContext

En el archivo, observe que se usa el mismo DbContext en Identity con la excepción de que extiende ApiAuthorizationDbContext (una clase más derivada de IdentityDbContext) para incluir el esquema para IdentityServer.

Para obtener el control total del esquema de la base de datos, herede de una de las clases de IdentityDbContext disponibles y configure el contexto para incluir el esquema de Identity llamando a builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) en el método OnModelCreating.

OidcConfigurationController

En el archivo, observe el punto de conexión que se aprovisiona para atender los parámetros de OIDC que el cliente necesita usar.

appsettings.json

En el archivo appsettings.json de la raíz del proyecto, hay una nueva sección de IdentityServer que describe la lista de clientes configurados. En el siguiente ejemplo hay un solo cliente, cuyo nombre corresponde al nombre de la aplicación y se asigna por convención al parámetro ClientId de OAuth. El perfil señala el tipo de aplicación que se está configurando. Se usa internamente para controlar las convenciones que simplifican el proceso de configuración del servidor. Hay varios perfiles disponibles, como se explica en la sección Perfiles de aplicación.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

En el archivo appsettings.Development.json de la raíz del proyecto, hay una sección de IdentityServer que describe la clave usada para firmar tokens. Al implementar en producción, es necesario aprovisionar e implementar una clave junto con la aplicación, como se explica en la sección Implementación en producción .

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descripción general de la aplicación de Angular

La compatibilidad con la autenticación y la autorización de API en la plantilla de Angular reside en su propio módulo de Angular en el directorio ClientApp/src/api-authorization. El módulo se compone de los siguientes elementos:

  • 3 componentes:
    • login.component.ts: controla el flujo de inicio de sesión de la aplicación.
    • logout.component.ts: controla el flujo de cierre de sesión de la aplicación.
    • login-menu.component.ts: un widget que muestra uno de los siguientes conjuntos de vínculos:
      • Administración de perfiles de usuario y vínculos de cierre de sesión cuando se autentique el usuario.
      • Registro y vínculos de inicio de sesión cuando el usuario no está autenticado.
  • Una restricción de rutas AuthorizeGuard que se puede agregar a las rutas y requiere que un usuario se autentique antes de visitar la ruta.
  • Un interceptor HTTP AuthorizeInterceptor que adjunta el token de acceso a las solicitudes HTTP salientes destinadas a la API cuando se autentica al usuario.
  • Un servicio AuthorizeService que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al resto de la aplicación para su consumo.
  • Un módulo de Angular que define las rutas asociadas a las partes de autenticación de la aplicación. Expone el componente de menú de inicio de sesión, el interceptor, la restricción y el servicio para su consumo desde el resto de la aplicación.

Descripción general de la aplicación de React

La compatibilidad con la autenticación y la autorización de API en la plantilla de React reside en el directorio ClientApp/src/components/api-authorization. Se compone de los siguientes elementos:

  • 4 componentes:
    • Login.js: controla el flujo de inicio de sesión de la aplicación.
    • Logout.js: controla el flujo de cierre de sesión de la aplicación.
    • LoginMenu.js: un widget que muestra uno de los siguientes conjuntos de vínculos:
      • Administración de perfiles de usuario y vínculos de cierre de sesión cuando se autentique el usuario.
      • Registro y vínculos de inicio de sesión cuando el usuario no está autenticado.
    • AuthorizeRoute.js: un componente de ruta que requiere que un usuario se autentique antes de representar el componente indicado en el parámetro Component.
  • Una instancia de authService exportada de la clase AuthorizeService que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al resto de la aplicación para su consumo.

Ahora que ha visto los componentes principales de la solución, puede profundizar en los escenarios individuales de la aplicación.

Requerir autorización en una nueva API

De forma predeterminada, el sistema está configurado para requerir fácilmente autorización para las nuevas API. Para ello, cree un nuevo controlador y agregue el atributo [Authorize] a la clase de controlador o a cualquier acción dentro del controlador.

Personalización del controlador de autenticación de API

Para personalizar la configuración del controlador JWT de la API, configure su instancia de JwtBearerOptions:

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

El controlador JWT de la API genera eventos que habilitan el control sobre el proceso de autenticación mediante JwtBearerEvents. Para proporcionar compatibilidad con la autorización de API, AddIdentityServerJwt registra sus propios controladores de eventos.

Para personalizar el control de un evento, encapsula el controlador de eventos existente con lógica adicional según sea necesario. Por ejemplo:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

En el código anterior, el controlador de eventos OnTokenValidated se reemplaza por una implementación personalizada. Esta implementación:

  1. Llama a la implementación original proporcionada por la compatibilidad con la autorización de API.
  2. Ejecute su propia lógica personalizada.

Protección de una ruta del lado cliente (Angular)

La protección de una ruta del lado cliente se realiza agregando la restricción de autorización a la lista de restricciones que se ejecutarán al configurar una ruta. Para obtener un ejemplo, puede ver cómo se configura la ruta fetch-data dentro del módulo de Angular de la aplicación principal:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

Es importante mencionar que la protección de una ruta no protege el punto de conexión real (que todavía requiere un atributo [Authorize] aplicado), pero que solo impide que el usuario navegue a la ruta del lado cliente dada cuando no se autentique.

Autenticación de solicitudes de API (Angular)

La autenticación de solicitudes a las API hospedadas junto con la aplicación se realiza automáticamente mediante el uso del interceptor de cliente HTTP definido por la aplicación.

Protección de una ruta del lado cliente (React)

Proteja una ruta del lado cliente mediante el componente AuthorizeRoute en lugar del componente sin formato Route. Por ejemplo, observe cómo se configura la ruta fetch-data dentro del componente App:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protección de una ruta:

  • No protege el punto de conexión real (que todavía requiere un atributo [Authorize] aplicado).
  • Solo impide que el usuario navegue a la ruta del lado cliente determinada cuando no se autentique.

Autenticación de solicitudes de API (React)

La autenticación de solicitudes con React se realiza importando primero la instancia de authService desde AuthorizeService. El token de acceso se recupera de authService y se adjunta a la solicitud, como se muestra a continuación. En los componentes de React, este trabajo se realiza normalmente en el método de ciclo de vida componentDidMount o como resultado de alguna interacción del usuario.

Importar authServiceen un componente

import authService from './api-authorization/AuthorizeService'

Recuperación y asociación del token de acceso a la respuesta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Implementación en producción

Para implementar la aplicación en producción, es necesario aprovisionar los siguientes recursos:

  • Una base de datos para almacenar las cuentas de usuario de Identity y las concesiones del servidor de Identity.
  • Un certificado de producción que se va a usar para firmar tokens.
    • No hay requisitos específicos para este certificado; puede ser un certificado autofirmado o un certificado aprovisionado a través de una entidad de CA.
    • Se puede generar a través de herramientas estándar como PowerShell o OpenSSL.
    • Se puede instalar en el almacén de certificados en las máquinas de destino o implementarse como un archivo .pfx con una contraseña segura.

Ejemplo: Implementación en un proveedor de hospedaje web que no es de Azure

En el panel de hospedaje web, cree o cargue el certificado. A continuación, en el archivo appsettings.json de la aplicación, modifique la sección IdentityServer para incluir los detalles clave. Por ejemplo:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

En el ejemplo anterior:

  • StoreName representa el nombre del almacén de certificados donde se almacena el certificado. En este caso, apunta al almacén de hospedaje web.
  • StoreLocation representa desde dónde cargar el certificado de (CurrentUser en este caso).
  • Name corresponde al sujeto distintivo del certificado.

Ejemplo: Implementación en Azure App Service

En esta sección se describe la implementación de la aplicación en Azure App Service mediante un certificado almacenado en el almacén de certificados. Para modificar la aplicación para cargar un certificado desde el almacén de certificados, se requiere un plan de servicio de nivel Estándar o superior al configurar la aplicación en el Azure Portal en un paso posterior.

En el archivo appsettings.json de la aplicación, modifique la sección IdentityServer para incluir los detalles clave:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • El nombre del almacén representa el nombre del almacén de certificados donde se almacena el certificado. En este caso, apunta al almacén personal del usuario.
  • La ubicación del almacén representa desde dónde cargar el certificado (CurrentUser o LocalMachine).
  • La propiedad name del certificado corresponde al sujeto distintivo del certificado.

Para realizar la implementación en Azure App Service, siga los pasos descritos en Implementación de la aplicación en Azure, que explica cómo crear los recursos de Azure necesarios e implementar la aplicación en producción.

Después de seguir las instrucciones anteriores, la aplicación se implementa en Azure, pero aún no es funcional. El certificado usado por la aplicación debe configurarse en el Azure Portal. Busque la huella digital del certificado y siga los pasos descritos en Carga de los certificados.

Aunque estos pasos mencionan SSL, hay una sección de Certificados privados en el Azure Portal donde puede cargar el certificado aprovisionado para usarlo con la aplicación.

Después de configurar la aplicación y la configuración de la aplicación en el Azure Portal, reinicie la aplicación en el portal.

Otras opciones de configuración

La compatibilidad con la autorización de API se basa en IdentityServer con un conjunto de convenciones, valores predeterminados y mejoras para simplificar la experiencia de las SPA. No es necesario decir que la potencia completa del Identityservidor está disponible en segundo plano si las integraciones de ASP.NET Core no cubren su escenario. El soporte técnico de ASP.NET Core se centra en las aplicaciones "de primera entidad", donde nuestra organización crea e implementa todas las aplicaciones. Por lo tanto, no se ofrece soporte técnico para aspectos como el consentimiento o la federación. Para esos escenarios, use IdentityServer y siga su documentación.

Perfiles de aplicación

Los perfiles de aplicación son configuraciones predefinidas para las aplicaciones que definen aún más sus parámetros. En este momento, se admiten los siguientes perfiles:

  • IdentityServerSPA: representa una SPA hospedada junto con IdentityServer como una sola unidad.
    • redirect_uri se establece de forma predeterminada en /authentication/login-callback.
    • post_logout_redirect_uri se establece de forma predeterminada en /authentication/logout-callback.
    • El conjunto de ámbitos incluye openid, profile y todos los ámbitos definidos para las API de la aplicación.
    • El conjunto de tipos de respuesta de OIDC permitidos es id_token token o cada uno de ellos individualmente (id_token, token).
    • El modo de respuesta permitido es fragment.
  • SPA: representa una SPA que no está hospedada con IdentityServer.
    • El conjunto de ámbitos incluye openid, profile y todos los ámbitos definidos para las API de la aplicación.
    • El conjunto de tipos de respuesta de OIDC permitidos es id_token token o cada uno de ellos individualmente (id_token, token).
    • El modo de respuesta permitido es fragment.
  • IdentityServerJwt: representa una API hospedada junto con IdentityServer.
    • La aplicación está configurada para tener un único ámbito que tenga como valor predeterminado el nombre de la aplicación.
  • API: representa una API que no está hospedada con IdentityServer.
    • La aplicación está configurada para tener un único ámbito que tenga como valor predeterminado el nombre de la aplicación.

Configuración a través de AppSettings

Configure las aplicaciones a través del sistema de configuración agregándolas a la lista de Clients o Resources.

Configure las propiedades redirect_uri y post_logout_redirect_uri de cada cliente, como se muestra en el ejemplo siguiente:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Al configurar los recursos, puede configurar los ámbitos del recurso, como se muestra a continuación:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuración mediante código

También puede configurar los clientes y recursos a través del código mediante una sobrecarga de AddApiAuthorization que realiza una acción para configurar las opciones.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Recursos adicionales

Las plantillas ASP.NET Core 3.1 y versiones posteriores ofrecen autenticación en aplicaciones de página única (SPA) mediante la compatibilidad con la autorización de API. ASP.NET Core Identity para autenticar y almacenar usuarios se combina con IdentityServer para implementar OpenID Connect.

Se agregó un parámetro de autenticación a las plantillas de proyecto de Angular y React que es similar al parámetro de autenticación de las plantillas de proyecto de la Aplicación web (Modelo-Vista-Controlador) (MVC) y Aplicación web (Páginas de Razor). Los valores de parámetro permitidos son Ninguno e Individual. La plantilla de proyecto React.js y Redux no admite el parámetro de autenticación en este momento.

Creación de una aplicación con compatibilidad con la autorización de API

La autenticación y autorización de usuario se pueden usar para las SPA tanto de Angular como de React. Abra un shell de comandos y ejecute el comando siguiente:

Angular:

dotnet new angular -o <output_directory_name> 

React:

dotnet new react -o <output_directory_name> -au Individual

El comando anterior crea una aplicación de ASP.NET Core con un directorio ClientApp que contiene la SPA.

Descripción general de los componentes de ASP.NET Core de la aplicación

En las siguientes secciones se describen algunas adiciones al proyecto cuando se incluye compatibilidad con la autenticación.

Clase Startup

Los siguientes ejemplos de código se basan en el paquete NuGet Microsoft.AspNetCore.ApiAuthorization.IdentityServer. En los ejemplos se configura la autenticación y autorización de API mediante los métodos de extensión AddApiAuthorization y AddIdentityServerJwt. Los proyectos que usan plantillas de proyecto de SPA de React o Angular con autenticación incluyen una referencia a este paquete.

La clase Startup tiene las siguientes adiciones:

  • Dentro del método Startup.ConfigureServices:

    • Identitycon la interfaz de usuario predeterminada:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer con un método auxiliar de AddApiAuthorization adicional que configura convenciones de ASP.NET Core predeterminadas por encima de IdentityServer:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • Autenticación con un método auxiliar AddIdentityServerJwt adicional que configura la aplicación para validar los tokens JWT generados por IdentityServer:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Dentro del método Startup.Configure:

    • El middleware de autenticación es responsable de validar las credenciales de solicitud y de configurar el usuario en el contexto de la solicitud:

      app.UseAuthentication();
      
    • El middleware de IdentityServer expone los puntos de conexión de OpenID Connect:

      app.UseIdentityServer();
      

Azure App Service en Linux

Para implementaciones en Linux de Azure App Service, especifique el emisor explícitamente en Startup.ConfigureServices:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

En el código anterior, el marcador de posición {AUTHORITY} es el Authority que se va a usar al realizar llamadas a OpenID Connect.

Ejemplo:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

Este método auxiliar configura IdentityServer para usar nuestra configuración admitida. IdentityServer es un marco eficaz y extensible que sirve para abordar los problemas de seguridad de las aplicaciones. Al mismo tiempo, expone complejidad innecesaria para los escenarios más comunes. En consecuencia, se le proporciona un conjunto de convenciones y opciones de configuración que consideramos un buen punto de partida. Cuando sus necesidades de autenticación cambien, tendrá a su disposición toda la eficacia de IdentityServer para personalizar la autenticación para adaptarse a sus necesidades.

AddIdentityServerJwt

Este método auxiliar configura un esquema de directiva para la aplicación como el controlador de autenticación predeterminado. La directiva está configurada para permitir que Identity controle todas las solicitudes enrutadas a cualquier subtrazado en el espacio de dirección URL de Identity "/Identity". JwtBearerHandler se encarga de todas las demás solicitudes. Además, este método registra un <<ApplicationName>>API recurso de API con IdentityServer con un ámbito predeterminado de <<ApplicationName>>API y configura el middleware de token de portador JWT para validar los tokens emitidos por IdentityServer para la aplicación.

WeatherForecastController

En el archivo, observe el atributo [Authorize] aplicado a la clase que indica que el usuario debe autorizarse en función de la directiva predeterminada para acceder al recurso. La directiva de autorización predeterminada se configura para usar el esquema de autenticación predeterminado, que se configura mediante AddIdentityServerJwt para el esquema de directiva mencionado anteriormente, lo que hace que el método auxiliar JwtBearerHandler configure el controlador predeterminado para las solicitudes a la aplicación.

ApplicationDbContext

En el archivo, observe que se usa el mismo DbContext en Identity con la excepción de que extiende ApiAuthorizationDbContext (una clase más derivada de IdentityDbContext) para incluir el esquema para IdentityServer.

Para obtener el control total del esquema de la base de datos, herede de una de las clases de IdentityDbContext disponibles y configure el contexto para incluir el esquema de Identity llamando a builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) en el método OnModelCreating.

OidcConfigurationController

En el archivo, observe el punto de conexión que se aprovisiona para atender los parámetros de OIDC que el cliente necesita usar.

appsettings.json

En el archivo appsettings.json de la raíz del proyecto, hay una nueva sección de IdentityServer que describe la lista de clientes configurados. En el siguiente ejemplo hay un solo cliente, cuyo nombre corresponde al nombre de la aplicación y se asigna por convención al parámetro ClientId de OAuth. El perfil señala el tipo de aplicación que se está configurando. Se usa internamente para controlar las convenciones que simplifican el proceso de configuración del servidor. Hay varios perfiles disponibles, como se explica en la sección Perfiles de aplicación.

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

En el archivo appsettings.Development.json de la raíz del proyecto, hay una sección de IdentityServer que describe la clave usada para firmar tokens. Al implementar en producción, es necesario aprovisionar e implementar una clave junto con la aplicación, como se explica en la sección Implementación en producción .

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Descripción general de la aplicación de Angular

La compatibilidad con la autenticación y la autorización de API en la plantilla de Angular reside en su propio módulo de Angular en el directorio ClientApp/src/api-authorization. El módulo se compone de los siguientes elementos:

  • 3 componentes:
    • login.component.ts: controla el flujo de inicio de sesión de la aplicación.
    • logout.component.ts: controla el flujo de cierre de sesión de la aplicación.
    • login-menu.component.ts: un widget que muestra uno de los siguientes conjuntos de vínculos:
      • Administración de perfiles de usuario y vínculos de cierre de sesión cuando se autentique el usuario.
      • Registro y vínculos de inicio de sesión cuando el usuario no está autenticado.
  • Una restricción de rutas AuthorizeGuard que se puede agregar a las rutas y requiere que un usuario se autentique antes de visitar la ruta.
  • Un interceptor HTTP AuthorizeInterceptor que adjunta el token de acceso a las solicitudes HTTP salientes destinadas a la API cuando se autentica al usuario.
  • Un servicio AuthorizeService que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al resto de la aplicación para su consumo.
  • Un módulo de Angular que define las rutas asociadas a las partes de autenticación de la aplicación. Expone el componente de menú de inicio de sesión, el interceptor, la restricción y el servicio para su consumo desde el resto de la aplicación.

Descripción general de la aplicación de React

La compatibilidad con la autenticación y la autorización de API en la plantilla de React reside en el directorio ClientApp/src/components/api-authorization. Se compone de los siguientes elementos:

  • 4 componentes:
    • Login.js: controla el flujo de inicio de sesión de la aplicación.
    • Logout.js: controla el flujo de cierre de sesión de la aplicación.
    • LoginMenu.js: un widget que muestra uno de los siguientes conjuntos de vínculos:
      • Administración de perfiles de usuario y vínculos de cierre de sesión cuando se autentique el usuario.
      • Registro y vínculos de inicio de sesión cuando el usuario no está autenticado.
    • AuthorizeRoute.js: un componente de ruta que requiere que un usuario se autentique antes de representar el componente indicado en el parámetro Component.
  • Una instancia de authService exportada de la clase AuthorizeService que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al resto de la aplicación para su consumo.

Ahora que ha visto los componentes principales de la solución, puede profundizar en los escenarios individuales de la aplicación.

Requerir autorización en una nueva API

De forma predeterminada, el sistema está configurado para requerir fácilmente autorización para las nuevas API. Para ello, cree un nuevo controlador y agregue el atributo [Authorize] a la clase de controlador o a cualquier acción dentro del controlador.

Personalización del controlador de autenticación de API

Para personalizar la configuración del controlador JWT de la API, configure su instancia de JwtBearerOptions:

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

El controlador JWT de la API genera eventos que habilitan el control sobre el proceso de autenticación mediante JwtBearerEvents. Para proporcionar compatibilidad con la autorización de API, AddIdentityServerJwt registra sus propios controladores de eventos.

Para personalizar el control de un evento, encapsula el controlador de eventos existente con lógica adicional según sea necesario. Por ejemplo:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

En el código anterior, el controlador de eventos OnTokenValidated se reemplaza por una implementación personalizada. Esta implementación:

  1. Llama a la implementación original proporcionada por la compatibilidad con la autorización de API.
  2. Ejecute su propia lógica personalizada.

Protección de una ruta del lado cliente (Angular)

La protección de una ruta del lado cliente se realiza agregando la restricción de autorización a la lista de restricciones que se ejecutarán al configurar una ruta. Para obtener un ejemplo, puede ver cómo se configura la ruta fetch-data dentro del módulo de Angular de la aplicación principal:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

Es importante mencionar que la protección de una ruta no protege el punto de conexión real (que todavía requiere un atributo [Authorize] aplicado), pero que solo impide que el usuario navegue a la ruta del lado cliente dada cuando no se autentique.

Autenticación de solicitudes de API (Angular)

La autenticación de solicitudes a las API hospedadas junto con la aplicación se realiza automáticamente mediante el uso del interceptor de cliente HTTP definido por la aplicación.

Protección de una ruta del lado cliente (React)

Proteja una ruta del lado cliente mediante el componente AuthorizeRoute en lugar del componente sin formato Route. Por ejemplo, observe cómo se configura la ruta fetch-data dentro del componente App:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

Protección de una ruta:

  • No protege el punto de conexión real (que todavía requiere un atributo [Authorize] aplicado).
  • Solo impide que el usuario navegue a la ruta del lado cliente determinada cuando no se autentique.

Autenticación de solicitudes de API (React)

La autenticación de solicitudes con React se realiza importando primero la instancia de authService desde AuthorizeService. El token de acceso se recupera de authService y se adjunta a la solicitud, como se muestra a continuación. En los componentes de React, este trabajo se realiza normalmente en el método de ciclo de vida componentDidMount o como resultado de alguna interacción del usuario.

Importar authServiceen un componente

import authService from './api-authorization/AuthorizeService'

Recuperación y asociación del token de acceso a la respuesta

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

Implementación en producción

Para implementar la aplicación en producción, es necesario aprovisionar los siguientes recursos:

  • Una base de datos para almacenar las cuentas de usuario de Identity y las concesiones del servidor de Identity.
  • Un certificado de producción que se va a usar para firmar tokens.
    • No hay requisitos específicos para este certificado; puede ser un certificado autofirmado o un certificado aprovisionado a través de una entidad de CA.
    • Se puede generar a través de herramientas estándar como PowerShell o OpenSSL.
    • Se puede instalar en el almacén de certificados en las máquinas de destino o implementarse como un archivo .pfx con una contraseña segura.

Ejemplo: Implementación en un proveedor de hospedaje web que no es de Azure

En el panel de hospedaje web, cree o cargue el certificado. A continuación, en el archivo appsettings.json de la aplicación, modifique la sección IdentityServer para incluir los detalles clave. Por ejemplo:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

En el ejemplo anterior:

  • StoreName representa el nombre del almacén de certificados donde se almacena el certificado. En este caso, apunta al almacén de hospedaje web.
  • StoreLocation representa desde dónde cargar el certificado de (CurrentUser en este caso).
  • Name corresponde al sujeto distintivo del certificado.

Ejemplo: Implementación en Azure App Service

En esta sección se describe la implementación de la aplicación en Azure App Service mediante un certificado almacenado en el almacén de certificados. Para modificar la aplicación para cargar un certificado desde el almacén de certificados, se requiere un plan de servicio de nivel Estándar o superior al configurar la aplicación en el Azure Portal en un paso posterior.

En el archivo appsettings.json de la aplicación, modifique la sección IdentityServer para incluir los detalles clave:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • El nombre del almacén representa el nombre del almacén de certificados donde se almacena el certificado. En este caso, apunta al almacén personal del usuario.
  • La ubicación del almacén representa desde dónde cargar el certificado (CurrentUser o LocalMachine).
  • La propiedad name del certificado corresponde al sujeto distintivo del certificado.

Para realizar la implementación en Azure App Service, siga los pasos descritos en Implementación de la aplicación en Azure, que explica cómo crear los recursos de Azure necesarios e implementar la aplicación en producción.

Después de seguir las instrucciones anteriores, la aplicación se implementa en Azure, pero aún no es funcional. El certificado usado por la aplicación debe configurarse en el Azure Portal. Busque la huella digital del certificado y siga los pasos descritos en Carga de los certificados.

Aunque estos pasos mencionan SSL, hay una sección de Certificados privados en el Azure Portal donde puede cargar el certificado aprovisionado para usarlo con la aplicación.

Después de configurar la aplicación y la configuración de la aplicación en el Azure Portal, reinicie la aplicación en el portal.

Otras opciones de configuración

La compatibilidad con la autorización de API se basa en IdentityServer con un conjunto de convenciones, valores predeterminados y mejoras para simplificar la experiencia de las SPA. No es necesario decir que la potencia completa del Identityservidor está disponible en segundo plano si las integraciones de ASP.NET Core no cubren su escenario. El soporte técnico de ASP.NET Core se centra en las aplicaciones "de primera entidad", donde nuestra organización crea e implementa todas las aplicaciones. Por lo tanto, no se ofrece soporte técnico para aspectos como el consentimiento o la federación. Para esos escenarios, use IdentityServer y siga su documentación.

Perfiles de aplicación

Los perfiles de aplicación son configuraciones predefinidas para las aplicaciones que definen aún más sus parámetros. En este momento, se admiten los siguientes perfiles:

  • IdentityServerSPA: representa una SPA hospedada junto con IdentityServer como una sola unidad.
    • redirect_uri se establece de forma predeterminada en /authentication/login-callback.
    • post_logout_redirect_uri se establece de forma predeterminada en /authentication/logout-callback.
    • El conjunto de ámbitos incluye openid, profile y todos los ámbitos definidos para las API de la aplicación.
    • El conjunto de tipos de respuesta de OIDC permitidos es id_token token o cada uno de ellos individualmente (id_token, token).
    • El modo de respuesta permitido es fragment.
  • SPA: representa una SPA que no está hospedada con IdentityServer.
    • El conjunto de ámbitos incluye openid, profile y todos los ámbitos definidos para las API de la aplicación.
    • El conjunto de tipos de respuesta de OIDC permitidos es id_token token o cada uno de ellos individualmente (id_token, token).
    • El modo de respuesta permitido es fragment.
  • IdentityServerJwt: representa una API hospedada junto con IdentityServer.
    • La aplicación está configurada para tener un único ámbito que tenga como valor predeterminado el nombre de la aplicación.
  • API: representa una API que no está hospedada con IdentityServer.
    • La aplicación está configurada para tener un único ámbito que tenga como valor predeterminado el nombre de la aplicación.

Configuración a través de AppSettings

Configure las aplicaciones a través del sistema de configuración agregándolas a la lista de Clients o Resources.

Configure las propiedades redirect_uri y post_logout_redirect_uri de cada cliente, como se muestra en el ejemplo siguiente:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

Al configurar los recursos, puede configurar los ámbitos del recurso, como se muestra a continuación:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

Configuración mediante código

También puede configurar los clientes y recursos a través del código mediante una sobrecarga de AddApiAuthorization que realiza una acción para configurar las opciones.

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

Recursos adicionales