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 de .NET 9 de este artículo.
Advertencia
Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 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 de .NET 9 de este artículo.
Identity de ASP.NET Core proporciona API que controlan la autenticación, la autorización y la administración de identity. 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 utilizar cookies, pero al utilizarla eres responsable de garantizar que los tokens se mantienen seguros. Recomendamos utilizar cookies para aplicaciones basadas en explorador, porque, de forma predeterminada, el explorador las gestiona automáticamente sin exponerlas a JavaScript.
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:
Microsoft.AspNetCore.Identity.EntityFrameworkCore
: permite que Identity trabaje con Entity Framework Core (EF Core).- Uno que permite que EF Core trabaje con una base de datos, como uno de los siguientes paquetes:
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, tanto las cookies como los tokens patentados están activados. Las cookies y los tokens se emiten en el inicio de 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 pueden eliminarse. Establezca
useCookies
entrue
.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 las cookies en el encabezado de forma predeterminada:
Si utilizas una herramienta para probar API, puede que tengas que habilitar las cookies en la configuración.
La API JavaScript
fetch
no incluye cookies de forma predeterminada. Habilítelos estableciendocredentials
en el valorinclude
de las opciones.Un elemento de
HttpClient
ejecutándose en una aplicación Blazor WebAssembly necesitaHttpRequestMessage
para incluir credenciales, como el siguiente ejemplo:request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
Uso de la autenticación basada en tokens
Recomendamos utilizar cookies en aplicaciones basadas en explorador, porque, de forma predeterminada, el explorador las gestiona automáticamente sin exponerlas a JavaScript.
Se emite un token personalizado (uno que es propiedad de la plataforma ASP.NET Core identity) que se puede usar para autenticar las solicitudes subsiguientes. 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 tokens web JSON (JWT) estándar. El uso de tokens personalizados es intencional, ya que la API integrada Identity está pensada principalmente para escenarios simples. La opción de tokens no está pensada para ser un proveedor de servicios de identity completo o un servidor de tokens, sino una alternativa a la opción de cookie para los clientes que no pueden usar cookies.
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();
Proporciona un objeto JSON vacío ({}
) en el cuerpo de la solicitud cuando llames 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:
POST /register
POST /login
POST /refresh
GET /confirmEmail
POST /resendConfirmationEmail
POST /forgotPassword
POST /resetPassword
POST /manage/2fa
GET /manage/info
POST /manage/info
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:
- Pruebe el registro anteriormente en este artículo.
- RegisterRequest.
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 entrue
para la autenticación basada en cookie. Establezcafalse
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.
Identity proporciona texto predeterminado para el correo electrónico de confirmación. 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>();
Para obtener más información, consulte Confirmación de la cuenta y recuperación de contraseñas en ASP.NET Core.
Identity proporciona texto predeterminado para los demás correos electrónicos que también deben enviarse, como en el caso del restablecimiento de contraseña y 2FA. Para personalizar estos correos electrónicos, proporcione una implementación personalizada de la interfaz IEmailSender
. 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:
Llama al punto de conexión de
/manage/2fa
y envía un objeto JSON ({}
) 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:
- Elegir una solución de administración de identity
- Soluciones de administración de Identity para aplicaciones web de .NET
- Autorización simple en ASP.NET Core
- Agregar, descargar y eliminar datos de usuario en Identity en un proyecto de ASP.NET Core
- Creación de una aplicación ASP.NET Core con datos de usuario protegidos por autorización
- Confirmación de las cuentas y recuperación de contraseñas en ASP.NET Core
- Habilitación de la generación de códigos QR para las aplicaciones de autenticación TOTP en ASP.NET Core
- Back-end de API web de ejemplo para SPA El archivo .http muestra la autenticación basada en tokens. Por ejemplo:
- No establece
useCookies
- Usa el encabezado de autorización para pasar el token
- Muestra la actualización para ampliar la sesión sin forzar al usuario a iniciar sesión de nuevo
- No establece
- Aplicación Angular de ejemplo que usa Identity para proteger un back-end de API web
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 pagues 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
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();
Advertencia
En este artículo se muestra el uso de cadena de conexión. Con una base de datos local, el usuario no tiene que autenticarse, pero en producción, cadena de conexión a veces incluye una contraseña para autenticarse. Una credencial de contraseña de propietario de recursos (ROPC) es un riesgo de seguridad que se debe evitar en las bases de datos de producción. Las aplicaciones de producción deben usar el flujo de autenticación más seguro disponible. Para obtener más información sobre la autenticación de aplicaciones implementadas para probar o entornos de producción, consulte Flujos de autenticación seguros.
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 cambien tus necesidades de autenticación, seguirás disponiendo de toda la eficacia de IdentityServer para personalizar la autenticación según tus 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 recurso de API <<ApplicationName>>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, observa que se usa el mismo elemento 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 salvaguarda 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 rest 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 salvaguarda y el servicio para el consumo desde el rest 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ámetroComponent
.
- Una instancia de
authService
exportada de la claseAuthorizeService
que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al rest 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:
- Llama a la implementación original proporcionada por la compatibilidad con la autorización de API.
- 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 salvaguarda de autorización a la lista de salvaguardas 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 authService
en 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 de IdentityServer.
- 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
oLocalMachine
). - 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. Por supuesto, toda la eficacia de IdentityServer está disponible entre bastidores si las integraciones de ASP.NET Core no cubren tu 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, utiliza IdentityServer y sigue 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
.
- El conjunto de ámbitos incluye
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. Identity de ASP.NET Core 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
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();
Advertencia
En este artículo se muestra el uso de cadena de conexión. Con una base de datos local, el usuario no tiene que autenticarse, pero en producción, cadena de conexión a veces incluye una contraseña para autenticarse. Una credencial de contraseña de propietario de recursos (ROPC) es un riesgo de seguridad que se debe evitar en las bases de datos de producción. Las aplicaciones de producción deben usar el flujo de autenticación más seguro disponible. Para obtener más información sobre la autenticación de aplicaciones implementadas para probar o entornos de producción, consulte Flujos de autenticación seguros.
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 cambien tus necesidades de autenticación, seguirás disponiendo de toda la eficacia de IdentityServer para personalizar la autenticación según tus 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 recurso de API <<ApplicationName>>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, observa que se usa el mismo elemento 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 salvaguarda 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 rest 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 salvaguarda y el servicio para el consumo desde el rest 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ámetroComponent
.
- Una instancia de
authService
exportada de la claseAuthorizeService
que controla los detalles de nivel inferior del proceso de autenticación y expone información sobre el usuario autenticado al rest 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:
- Llama a la implementación original proporcionada por la compatibilidad con la autorización de API.
- 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 salvaguarda de autorización a la lista de salvaguardas 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 authService
en 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 de IdentityServer.
- 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
oLocalMachine
). - 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. Por supuesto, toda la eficacia de IdentityServer está disponible entre bastidores si las integraciones de ASP.NET Core no cubren tu 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, utiliza IdentityServer y sigue 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
.
- El conjunto de ámbitos incluye
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"));
});