Ejemplo en una aplicación
En este segmento, procederé a construir una aplicación de ejemplo, incorporaré todos los paquetes y servicios esenciales y, al final, estableceré las API de identidad. Según mi análisis, actualmente, las plantillas dotnet new no incluyen las API de identidad.
La plantilla webapi ofrece la opción –auth, pero en la actualidad, su compatibilidad se limita a Azure AD, Azure AD B2C o autenticación de Windows. Anticipo una futura actualización de esta plantilla para incluir la opción Individual mediante la implementación de las API de identidad en .NET 8.
Comenzaremos creando un archivo nuevo con dotnet new webapi. He utilizado el SDK de .NET 8 para todas las directrices mencionadas en este artículo.
dotnet new webapi
Para agregar las API de identidad necesitamos hacer varias cosas:
- Agregue los paquetes necesarios
- Adición de EF Core
- Agregue los modelos de Identity EF Core necesarios y genere migraciones
- Adición de las API y los servicios de identidad
- Adición de servicios de autorización y middleware
Seguiré cada uno de esos pasos en las siguientes secciones.
Agregar de los paquetes de EF Core
Comenzaremos incorporando EF Core a nuestra aplicación, un paso que involucra la inclusión de varios paquetes.
Para esta exhibición, elegiré SQLite como la base de datos de respaldo debido a su simplicidad. Aunque no es recomendable para implementaciones en producción de aplicaciones web, es adecuado para pruebas como la que estamos llevando a cabo.
Iniciaremos la inclusión de los paquetes necesarios.
# The main package for SQLite EF Core support dotnet add package Microsoft.EntityFrameworkCore.SQLite --prerelease # Contains shared build-time components for EF Core dotnet add package Microsoft.EntityFrameworkCore.Design --prerelease # The ASP.NET Core Identity integration for EF Core dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --prerelease
He utilizado la marca –prerelease para asegurarse de obtener los paquetes más recientes (los paquetes de .NET 8). También deberás instalar la herramienta ef si aún no lo has hecho. Personalmente, actualicé la herramienta a la versión más reciente (versión de .NET 8) de la siguiente manera:
dotnet tool update --global dotnet-ef --prerelease
En este momento, contamos con todas las herramientas y paquetes necesarios, por lo tanto, incorporaremos EF Core a nuestra aplicación.
Configuración de EF Core en la aplicación
Para iniciar la integración con EF Core, es necesario contar con una implementación de DbContext en nuestra aplicación. La implementación más elemental de DbContext se presenta de la siguiente manera (es importante destacar que la ajustaremos pronto para ofrecer soporte a Identity).
public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } }
Podemos registrarnos AppDbContext en nuestra aplicación llamando AddDbContext<> a en WebApplicationBuilder :
using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); // Add EF Core builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); // ...
En la porción de código mencionada anteriormente, hemos ajustado la configuración de EF Core para que emplee nuestra clase AppDbContext, optando por utilizar SQLite y especificando una cadena de conexión denominada DefaultConnection. La definición de la cadena de conexión puede llevarse a cabo en appsettings.json (cabe mencionar que no he expuesto la configuración previa, solo la nueva entrada):
{ "ConnectionStrings": { "DefaultConnection": "Data Source=my_test_app.db" } }
Eso está configurado con EF Core, por lo que ahora podemos configurar ASP.NET identidad de Core.
Agregar Identity y los servicios de punto de conexión de la API de identidad
Antes de incorporar los servicios de identidad, es imperativo realizar dos acciones previas
- Cree un tipo de usuario que se derive de IdentityUser
- Actualice nuestro para que se derive de AppDbContextIdentityDbContext<>
El primer punto no es estrictamente necesario, ya que puedes utilizar IdentityUser directamente en tus aplicaciones si es preciso. No obstante, dado que es probable que desees personalizar tu tipo de usuario en algún momento, considero que tiene sentido optar por un tipo personalizado más pronto que tarde.
public class AppUser : IdentityUser { // Add customisations here later } // Change from DbContext to IdentityDbContext<> public class AppDbContext : IdentityDbContext<AppUser> { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } }
Con la existencia de estos tipos en la aplicación, se abre la oportunidad de agregar los servicios de identidad mediante el recién incorporado método AddIdentityApiEndpoints<> y configurar los almacenes estándar de Identity en EF Core:
builder.Services .AddIdentityApiEndpoints<AppUser>() .AddEntityFrameworkStores<AppDbContext>();
El método AddIdentityApiEndpoints<> hace varias cosas:
- Configura la autenticación a través de tokens y cookies (se proporcionará mayor información sobre la autenticación de cookies en publicaciones posteriores).
- Agrega los servicios clave de identidad, como el UserManager.
- También, incluye los servicios necesarios para los puntos de conexión de la API de identidad, como los proveedores de tokens SignInManager y una implementación sin operaciones de IEmailSender.
Una vez que hemos integrado todos estos servicios, finalmente podemos ejecutar algunas migraciones y establecer la base de datos.
Creación de la base de datos
Dado que la aplicación se vale de EF Core, resulta necesario generar una migración y actualizar la base de datos. Si la compilación de la aplicación se realiza sin inconvenientes, deberías ser capaz de llevar a cabo ambos pasos con el siguiente comando:
dotnet ef migrations add InitialSchema dotnet ef database update
Si todo procede según lo esperado, esto debería conllevar la creación del archivo de base de datos SQLite llamado my_test_app.db. Estamos a punto de poner a prueba nuestra aplicación.
Agregar autorización a una API
En última instancia, nuestro objetivo es asegurar nuestras API mediante la autorización. Para garantizar un funcionamiento correcto, añadamos un requisito de autorización al punto de acceso correspondiente al pronóstico del tiempo:
app.MapGet("/weatherforecast", () => /* not show for brevity */) .WithName("GetWeatherForecast") .RequireAuthorization() // 👈 Add this .WithOpenApi();
En caso de ejecutar la aplicación y tratar de llegar a este punto de conexión, te enfrentarás a un error que señalará la ausencia del middleware de autorización en la aplicación.
System.InvalidOperationException: Endpoint HTTP: GET /weatherforecast contains authorization metadata, but a middleware was not found that supports authorization. Configure your application startup by adding app.UseAuthorization() in the application startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), the call to app.UseAuthorization() must go between them.
Por lo tanto, para abordar esta situación, incluye los servicios de autorización y agrega el middleware de autorización en WebApplicationBuilder. En este punto, la aplicación debería mostrar una apariencia similar a la siguiente:
var builder = WebApplication.CreateBuilder(args); // Add the authorization services builder.Services.AddAuthorization(); // Add EF Core builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); // add identity services builder.Services .AddIdentityApiEndpoints<AppUser>() .AddEntityFrameworkStores<AppDbContext>(); // Swagger/OpenAPI services builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); // 👈 Add Authorization middleware // ...
Agregar puntos de conexión de la API de identidad
Para incluir los puntos de conexión de identidad en la aplicación, invoca MapIdentityApi dentro de MapIdentityApi. Esto introduce una amplia variedad de puntos de acceso en la aplicación (que se detallarán en breve).
Es probable que desees restringirlos a una subruta como /account o /identity, de manera que en lugar de los puntos de conexión /login y /confirmEmail, tendrás /account/login y /account/confirmEmail, por ejemplo. Puedes añadir el prefijo mediante MapGroup, como se ejemplifica en el siguiente caso:
app.MapGroup("/account").MapIdentityApi<AppUser>();
Con esto, ¡hemos completado el proceso! Ahora es el momento de sumergirse en la aplicación y descubrir qué capacidades tiene para ofrecer.
API de identidad
La forma más cómoda de visualizar los puntos de conexión disponibles es ejecutar la aplicación y dirigirse a /swagger/index.html para examinar la documentación de SwaggerUI:
En la parte superior de la lista, se encuentra la API /weatherforecast, y debajo de ella están todos los puntos finales agregados por MapIdentityApi<>(). Si bien es posible utilizar SwaggerUI para interactuar con la API, opté por no perder tiempo con tokens en la interfaz de usuario, y en su lugar, decidí utilizar el soporte integrado de Rider para HttpClient.
Prueba del punto de conexión protegido
El detalle inicial que atrajo mi atención fue que la ventana de Puntos finales de Rider detectaba el punto final /weatherforecast, pero no identificaba los puntos finales de identidad, incluso con la opción Mostrar desde bibliotecas habilitada. A pesar de esto, es el primer punto de acceso que deseamos verificar para asegurarnos de su seguridad. Al hacer clic derecho en el punto de acceso, opté por Generar solicitud en el cliente HTTP:
Esto generó una simple solicitud ‘GET» al punto de conexión en un archivo .http:
Al hacer clic en el ícono de Ejecutar en el medio, se lanza la solicitud al punto de acceso y, como era de anticipar, se recibe una respuesta Unauthorized, indicando que la API estaba protegida correctamente:
GET http://localhost:5117/weatherforecast HTTP/1.1 401 Unauthorized Content-Length: 0 Date: Sat, 02 Sep 2023 19:46:02 GMT Server: Kestrel WWW-Authenticate: Bearer
El primer paso para llamar a este punto de conexión es crear un usuario con el que podamos iniciar sesión.
Registrar usuario nuevo
El primer punto de conexión que exploraremos es el punto de acceso /register (expuesto en mi ejemplo como /account/register). Se trata de un punto final POST al que enviamos una dirección de correo electrónico, un nombre de usuario y una contraseña:
### Register a new user POST http://localhost:5117/account/register Content-Type: application/json { "username": "andrew@example.com", "password": "SuperSecret1!", "email": "andrew@example.com" }
Si esta acción tiene éxito, recibirás una respuesta 200. Ten en cuenta que la contraseña que envíes debe cumplir con todos los requisitos estándar de IdentityOptions; de lo contrario, será rechazada si no satisface los criterios de longitud o complejidad, por ejemplo.
Recuperación de un token de acceso
Si el usuario ha sido creado exitosamente, el paso siguiente implica obtener un token de acceso. Podemos utilizar la ruta /login para llevar a cabo esta operación.
### Login and retrieve tokens POST http://localhost:5117/account/login Content-Type: application/json { "username": "andrew@example.com", "password": "SuperSecret1!" }
Si proporciona un nombre de usuario y una contraseña válidos, esto devuelve una respuesta similar a la que vio anteriormente:
{ "token_type": "Bearer", "access_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0jen4IyKKwsovVDRrrFyC_nU4HRXb...", "expires_in": 3600, "refresh_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0gvL1EYfbVBnppccNrI6WrfRcsOb..." }
Podemos usar el scripting integrado de Rider HttpClient para tomar automáticamente este token de la respuesta y guardarlo en una variable que podemos usar más tarde:
> {% client.global.set("access_token", response.body.access_token); client.global.set("refresh_token", response.body.refresh_token); %}
Así que todo se ve así en Rider:
Llamar a la API protegida
Ahora que contamos con un access_token, podemos emplearlo para realizar solicitudes a la API protegida:
### Call Forecast API with bearer token GET http://localhost:5117/weatherforecast Authorization: Bearer {{access_token}}
Json devuelto:
[ { "date": "2023-09-03", "temperatureC": 20, "summary": "Chilly", "temperatureF": 67 }, ... ]
Generación de un token de actualización
En el transcurso del tiempo, el token de acceso expirará, lo que nos llevará a buscar uno nuevo. Esta tarea se puede llevar a cabo utilizando el endpoint /refresh.
### Fetch a new access token POST http://localhost:5117/account/refresh Content-Type: application/json { "refreshToken": "{{refresh_token}}" }
Hay una variedad de otros puntos de acceso a los que puedes acceder para administrar tokens 2FA para el usuario, entre otras cosas, pero dado que esta publicación ya es lo suficientemente larga.
Conclusiones
En resumen, en esta publicación se ha explorado exhaustivamente el estado actual de ASP.NET Identity y las diversas abstracciones que ofrece, destacando la funcionalidad proporcionada por la interfaz de usuario predeterminada de Razor Pages. Sin embargo, se han señalado las limitaciones y complejidades asociadas con el estilo y la integración con SPA o aplicaciones móviles.
Posteriormente, se proporcionó una guía detallada sobre la incorporación de ASP.NET Identity a una nueva aplicación, abordando la configuración desde una plantilla en blanco e incluyendo EF Core, Identity y las API correspondientes. Se demostró cómo interactuar con estas APIs utilizando un cliente HTTP. Estas reflexiones ofrecen una visión completa de ASP.NET Identity y sirven como guía para su implementación efectiva en aplicaciones nuevas o existentes.