Protección de ASP.NET Core Blazor WebAssembly con ASP.NET Core Identity

Las aplicaciones independientes Blazor WebAssembly se pueden proteger con ASP.NET Core Identity siguiendo las instrucciones de este artículo.

Puntos de conexión para registrarse, iniciar sesión y cerrar sesión

En lugar de usar la interfaz de usuario predeterminada proporcionada por ASP.NET Core Identity para SPA y aplicaciones Blazor, que se basa en Páginas Razor, llame a MapIdentityApi en una API de back-end para agregar JSpuntos de conexión de API ON para registrar e iniciar sesión en usuarios con ASP.NET Core Identity. Identity Los puntos de conexión de API también admiten características avanzadas, como la autenticación en dos fases y la comprobación de correo electrónico.

En el cliente, llame al punto de conexión /register para registrar un usuario con su dirección de correo electrónico y contraseña:

var result = await _httpClient.PostAsJsonAsync(
    "register", new
    {
        email,
        password
    });

En el cliente, inicie sesión en un usuario con autenticación cookie mediante el punto de conexión /login con la cadena de consulta useCookies establecida en true:

var result = await _httpClient.PostAsJsonAsync(
    "login?useCookies=true", new
    {
        email,
        password
    });

La API del servidor back-end establece la autenticación cookie con una llamada a AddIdentityCookies en el generador de autenticación:

builder.Services
    .AddAuthentication(IdentityConstants.ApplicationScheme)
    .AddIdentityCookies();

Autenticación por tokens

En el caso de escenarios nativos y móviles donde algunos clientes no admiten cookies, 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 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 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.

La siguiente guía comienza el proceso de implementación de la autenticación basada en tokens con la API de inicio de sesión. Se requiere código personalizado para completar la implementación. Para más información, consulte Uso de Identity para proteger un back-end de API web para SPA.

En lugar de la API del servidor back-end que establece la autenticación cookie con una llamada a AddIdentityCookies en el generador de autenticación, la API de servidor configura la autenticación de token de portador con el método de extensión AddBearerToken. Especifique el esquema de los tokens de autenticación de portador con IdentityConstants.BearerScheme.

En Backend/Program.cs, cambie los servicios de autenticación y la configuración por lo siguiente:

builder.Services
    .AddAuthentication()
    .AddBearerToken(IdentityConstants.BearerScheme);

En BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs, quite el parámetro de cadena de consulta useCookies en el método LoginAsync de CookieAuthenticationStateProvider:

- login?useCookies=true
+ login

En este momento, debe proporcionar código personalizado para analizar AccessTokenResponse en el cliente y administrar los tokens de acceso y actualización. Para más información, consulte Uso de Identity para proteger un back-end de API web para SPA.

Otros escenarios de Identity

Para información sobre otros escenarios de Identity proporcionados por la API, consulte Uso de Identity para proteger un back-end de API web para SPA:

  • Protección de los puntos de conexión seleccionados
  • Autenticación por tokens
  • Autenticación en dos fases (2FA)
  • Los códigos de recuperación
  • Administración de la información del usuario

Aplicaciones de muestra

En este artículo, las aplicaciones de ejemplo sirven como referencia para las aplicaciones independientes Blazor WebAssembly que acceden a ASP.NET Core Identity a través de una API web de back-end. La demostración incluye dos aplicaciones:

  • Backend: una aplicación de API web de back-end que mantiene un almacén de identidades de usuario para ASP.NET Core Identity.
  • BlazorWasmAuth: una aplicación de front-end Blazor WebAssembly independiente con autenticación de usuario.

Acceda a las aplicaciones de ejemplo a través de la carpeta de versión más reciente desde la raíz del repositorio con el vínculo siguiente. Los ejemplos se proporcionan para .NET 8 o posterior. Consulte el archivo README en la carpeta BlazorWebAssemblyStandaloneWithIdentity para ver los pasos sobre cómo ejecutar las aplicaciones de ejemplo.

Vea o descargue el código de ejemplo (cómo descargarlo)

Paquetes y código de la aplicación de API web de back-end

La aplicación de API web de back-end mantiene un almacén de identidades de usuario para ASP.NET Core Identity.

Paquetes

La aplicación usa los siguientes paquetes NuGet:

Si la aplicación va a usar un proveedor de base de datos de EF Core diferente al proveedor en memoria, no cree una referencia de paquete en la aplicación para Microsoft.EntityFrameworkCore.InMemory.

En el archivo de proyecto de la aplicación (.csproj), se configura la globalización invariante.

Código de aplicación de ejemplo

La configuración de la aplicación configurar direcciones URL de back-end y front-end:

  • Aplicación de Backend (BackendUrl): https://localhost:7211
  • Aplicación deBlazorWasmAuth (FrontendUrl): https://localhost:7171

El Backend.http archivo se puede usar para probar la solicitud de datos meteorológicos. Tenga en cuenta que la aplicación BlazorWasmAuth debe ejecutarse para probar el punto de conexión y el punto de conexión está codificado de forma dura en el archivo. Para obtener más información, vea Usar archivos .http en Visual Studio 2022.

La siguiente instalación y configuración se encuentra en el Programarchivo de la aplicación.

La identidad de usuario con la autenticación de cookie se agrega mediante una llamada a AddAuthentication y AddIdentityCookies. Los servicios para comprobaciones de autorización se agregan mediante una llamada a AddAuthorizationBuilder.

Solo recomendada para demostraciones, la aplicación utiliza el EF Core proveedor de base de datos en memoria para el registro del contexto de la base de datos (AddDbContext). El proveedor de bases de datos en memoria facilita el reinicio de la aplicación y prueba los flujos de usuario de registro e inicio de sesión. Cada ejecución comienza con una base de datos nueva, pero la aplicación incluye código de demostración de la inicialización de un usuario de prueba, el cual se describe más adelante en este artículo. Si la base de datos se cambia a SQLite, los usuarios se guardan entre sesiones, pero la base de datos debe crearse mediante migraciones, como se muestra en el tutorial de introducción a EF Core. Puede usar otros proveedores relacionales como SQL Server para el código de producción.

Configure Identity para usar la base de datos de EF Core y exponer los puntos de conexión de Identity a través de las llamadas a AddIdentityCore, AddEntityFrameworkStores y AddApiEndpoints.

Se establece una directiva de uso compartido de recursos entre orígenes (CORS) para permitir solicitudes de las aplicaciones front-end y back-end. Las direcciones URL de reserva están configuradas para la directiva CORS si la configuración de la aplicación no las proporciona:

  • Aplicación de Backend (BackendUrl): https://localhost:5001
  • Aplicación de BlazorWasmAuth (FrontendUrl): https://localhost:5002

Los servicios y los puntos de conexión para Swagger/OpenAPI se incluyen para la documentación de la API web y las pruebas de desarrollo. Para más información sobre NSwag, consulte Introducción a NSwag y ASP.NET Core.

Las notificaciones de rol de usuario se envían desde una API mínima en el punto de conexión /roles.

Las rutas se asignan para los puntos de conexión de Identity mediante una llamada a MapIdentityApi<AppUser>().

Un punto de conexión de cierre de sesión (/Logout) está configurado en la canalización de middleware para cerrar la sesión de los usuarios.

Para proteger un punto de conexión, agregue el método de extensión RequireAuthorization a la definición de ruta. Para un controlador, agregue el [Authorize] atributo al controlador o a la acción.

Para obtener más información sobre los patrones básicos para la inicialización y configuración de una instancia DbContext, consulte Duración, configuración e inicialización de DbContext en la documentación EF Core.

Paquetes y código de aplicaciones de Blazor WebAssembly independientes de front-end

Una aplicación de front-end de Blazor WebAssembly independiente muestra la autenticación y autorización de usuario para acceder a una página web privada.

Paquetes

La aplicación usa los siguientes paquetes NuGet:

Código de aplicación de ejemplo

La carpeta Models contiene los modelos de la aplicación:

La interfaz IAccountManagement (Identity/CookieHandler.cs) proporciona servicios de administración de cuentas.

La clase CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs) controla el estado de la autenticación basada en cookie y proporciona implementaciones del servicio de administración de cuentas descritas por la interfaz IAccountManagement. El método LoginAsync habilita explícitamente la autenticación cookie a través del valor de cadena de consulta useCookies de true. La clase también administra la creación de notificaciones de rol para usuarios autenticados.

La clase CookieHandler (Identity/CookieHandler.cs) garantiza cookie credenciales se envían con cada solicitud a la API web de back-end, que controla Identity y mantiene el almacén de datos Identity.

wwwroot/appsettings.file proporciona puntos de conexión de URL de back-end y front-end.

El App componente expone el estado de autenticación como parámetro en cascada. Para obtener más información, consulte Autenticación y autorización Blazor de ASP.NET Core.

El MainLayout componente y NavMenu componente usar el AuthorizeView componente para mostrar de forma selectiva el contenido en función del estado de autenticación del usuario.

Los siguientes componentes controlan las tareas comunes de autenticación de usuario, haciendo uso de servicios IAccountManagement :

El PrivatePage componente (Components/Pages/PrivatePage.razor) requiere autenticación y muestra las notificaciones del usuario.

Los servicios y la configuración se proporcionan en el Program archivo (Program.cs):

  • El controlador cookie se registra como un servicio con ámbito.
  • Los servicios de autorización están registrados.
  • El proveedor de estado de autenticación personalizado se registra como un servicio con ámbito.
  • La interfaz de administración de cuentas (IAccountManagement) está registrada.
  • La dirección URL del host base está configurada para una instancia de cliente HTTP registrada.
  • La dirección URL de back-end base está configurada para una instancia de cliente HTTP registrada que se usa para las interacciones de autenticación con la API web de back-end. El cliente HTTP usa el controlador cookie para asegurarse de que cookie credenciales se envían con cada solicitud.

Llame a AuthenticationStateProvider.NotifyAuthenticationStateChanged cuando cambie el estado de autenticación del usuario. Para obtener un ejemplo, vea los métodos LoginAsync y LogoutAsync de la clase CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs).

Advertencia

El componente AuthorizeView selectivamente muestra el contenido de la interfaz de usuario dependiendo de si el usuario tiene la autorización necesaria. Todo el contenido de una aplicación Blazor WebAssembly colocada en un componente de AuthorizeView se puede detectar sin autenticación, por lo que el contenido confidencial debe obtenerse de una API web basada en servidor back-end después de que la autenticación se realice correctamente. Para obtener más información, consulte los siguientes recursos:

Demostración de la inicialización de un usuario de prueba

La clase SeedData (SeedData.cs) muestra cómo crear usuarios de prueba para el desarrollo. El usuario de prueba, llamado Leela, inicia sesión en la aplicación con la dirección de correo electrónico leela@contoso.com. La contraseña del usuario se establece en Passw0rd!. Se le asignan roles Administrator y Manager para la autorización, lo que permite al usuario acceder a la página del administrador en /private-manager-page, pero no a la página del editor en /private-editor-page.

Advertencia

Nunca permita que el código del usuario de prueba se ejecute en un entorno de producción. Solo se llama a SeedData.InitializeAsync en el entorno Development en el archivo Program:

if (builder.Environment.IsDevelopment())
{
    await using var scope = app.Services.CreateAsyncScope();
    await SeedData.InitializeAsync(scope.ServiceProvider);
}

Roles

Debido a un problema de diseño del marco (dotnet/aspnetcore #50037), las notificaciones de rol no se envían desde el punto de conexión manage/info para crear notificaciones de usuario para los usuarios de la aplicación BlazorWasmAuth. Las notificaciones de rol se administran de forma independiente por medio de una solicitud independiente en el método GetAuthenticationStateAsync de la clase CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs) después de que el usuario se autentique en el proyecto Backend.

En CookieAuthenticationStateProvider, se realiza una solicitud de roles al punto de conexión /roles del proyecto de API del servidor Backend. La respuesta se lee en una cadena llamando a ReadAsStringAsync(). JsonSerializer.Deserialize deserializa la cadena en una matriz RoleClaim personalizada. Por último, las notificaciones se agregan a la colección de notificaciones del usuario.

En la API del servidor Backend, en el archivo Program, una API mínima administra el punto de conexión /roles. Las notificaciones de RoleClaimType se seleccionanen un tipo anónimo y se serializan para devolverlas al proyecto BlazorWasmAuth con TypedResults.Json.

El punto de conexión /roles requiere autorización llamando a RequireAuthorization. Si decide no usar las API mínimas en favor de los controladores para los puntos de conexión de API de servidor seguro, asegúrese de establecer el atributo [Authorize] en controladores o acciones.

Hospedaje entre dominios (configuración del mismo sitio)

Las aplicaciones de ejemplo están configuradas para hospedar ambas aplicaciones en el mismo dominio. Si hospeda la aplicación de Backend en un dominio diferente al BlazorWasmAuth aplicación, quite la marca de comentario del código que configura el cookie (ConfigureApplicationCookie) en el archivoBackend de la aplicaciónProgram. Los valores predeterminados son:

Cambie los valores a:

- options.Cookie.SameSite = SameSiteMode.Lax;
- options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
+ options.Cookie.SameSite = SameSiteMode.None;
+ options.Cookie.SecurePolicy = CookieSecurePolicy.Always;

Para obtener más información sobre la configuración cookie del mismo sitio, vea los siguientes recursos:

Compatibilidad con antiforgería

Solo el punto de conexión de cierre de sesión (/logout) de la aplicación Backend requiere atención para mitigar la amenaza de falsificación de solicitud entre sitios (CSRF).

El punto de conexión de cierre de sesión comprueba si hay un cuerpo vacío para evitar ataques CSRF. Al requerir un cuerpo, la solicitud debe realizarse desde JavaScript, que es la única manera de acceder a la autenticación cookie. No se puede acceder al punto de conexión de cierre de sesión mediante post basado en formularios. Esto impide que un sitio malintencionado inicie la sesión del usuario.

Además, el punto de conexión está protegido por autorización (RequireAuthorization) para evitar el acceso anónimo.

La aplicación cliente BlazorWasmAuth simplemente es necesaria para pasar un objeto vacío {} en el cuerpo de la solicitud.

Fuera del punto de conexión de cierre de sesión, la mitigación antiforgería solo es necesaria al enviar datos de formulario al servidor codificado como application/x-www-form-urlencoded, multipart/form-datao text/plain. Blazor administra la mitigación de CSRF para formularios en la mayoría de los casos. Para obtener más información, consulte Autenticación y autorizaciónBlazor ASP.NET Core e Información general sobre formularios BlazorASP.NET Core.

Las solicitudes a otros puntos de conexión de API de servidor (API web) con contenido codificado application/jsony CORS habilitado no requieren protección CSRF. Este es el motivo por el que no se requiere ninguna protección CSRF para elBackend punto de conexión de procesamiento de datos de la aplicación(/data-processing). Los roles (/roles) punto de conexión no necesitan protección CSRF porque es un punto de conexión GET que no modifica ningún estado.

Solución de problemas

Registro

Para habilitar el registro de depuración o seguimiento para la autenticación de Blazor WebAssembly, consulte el registro de Blazor en ASP.NET Core.

Errores comunes

Compruebe la configuración de cada proyecto. Confirme que las direcciones URL son correctas:

  • Proyecto Backend
    • appsettings.json
      • BackendUrl
      • FrontendUrl
    • Backend.http: Backend_HostAddress
  • Proyecto BlazorWasmAuth: wwwroot/appsettings.json
    • BackendUrl
    • FrontendUrl

Si la configuración parece correcta:

  • Analice los registros de la aplicación.

  • Examine el tráfico de red entre la aplicación BlazorWasmAuth y la aplicación Backend con las herramientas de desarrollo del explorador. A menudo, después de realizar la solicitud, la aplicación de back-end devuelve al cliente un mensaje de error exacto o un mensaje con una pista sobre la causa del problema. En los siguientes artículos encontrará instrucciones sobre las herramientas de desarrollo:

  • Google Chrome (documentación de Google)

  • Microsoft Edge

  • Mozilla Firefox (documentación de Mozilla)

El equipo de documentación responde a los comentarios y errores de los documentos en los artículos. Abra una incidencia con el vínculo Abrir una incidencia de documentación de la parte inferior del artículo. El equipo no puede proporcionar soporte técnico para el producto. Existen varios foros de soporte técnico públicos que ayudan a solucionar los problemas de una aplicación. Se recomienda lo siguiente:

Microsoft no posee ni controla ninguno de los foros anteriores.

Respecto a los informes de errores del marco que no son de seguridad ni confidenciales, o que no se pueden reproducir, abra una incidencia con la unidad de producto ASP.NET Core. No abra una incidencia con la unidad de producto hasta que haya investigado exhaustivamente su causa y no pueda resolverlo por su cuenta o con la ayuda de la comunidad en un foro de soporte técnico público. La unidad de producto no puede solucionar problemas de aplicaciones individuales cuyo funcionamiento se haya interrumpido debido a errores de configuración o casos de uso sencillos que involucren servicios de terceros. Si un informe es confidencial o delicado por naturaleza o describe un posible error de seguridad en el producto que los atacantes pueden aprovechar, vea Informes de problemas de seguridad y errores (repositorio de GitHub dotnet/aspnetcore).

Cookies y datos del sitio

Las Cookies y los datos del sitio pueden persistir entre las actualizaciones de la aplicación e interferir con las pruebas y la solución de problemas. Al realizar cambios en el código de la aplicación, cambios en la cuenta de usuario o cambios en la configuración de la aplicación, borre lo siguiente:

  • cookies de inicio de sesión del usuario
  • cookies de aplicaciones
  • Datos de sitios almacenados y en caché

El enfoque siguiente sirve para evitar que las cookies persistentes y los datos del sitio interfieran con las pruebas y la solución de problemas:

  • Configuración de un explorador
    • Use un explorador para las pruebas, y configúrelo para que elimine todas las cookies y los datos del sitio cada vez que se cierre.
    • Asegúrese de que el explorador se cierra manualmente o mediante el IDE siempre que se produzca cualquier cambio en la aplicación, el usuario de prueba o la configuración del proveedor.
  • Use un comando personalizado para abrir un explorador en el modo incógnito o privado en Visual Studio:
    • Abra el cuadro de diálogo Examinar con mediante el botón Ejecutar de Visual Studio.
    • Seleccione el botón Agregar.
    • Proporcione la ruta de acceso al explorador en el campo Programa. Las siguientes rutas de acceso del archivo ejecutable son ubicaciones de instalación típicas para Windows 10. Si el explorador está instalado en una ubicación diferente o no usa Windows 10, proporcione la ruta de acceso al archivo ejecutable del explorador.
      • Microsoft Edge: C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
      • Google Chrome: C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
      • Mozilla Firefox: C:\Program Files\Mozilla Firefox\firefox.exe
    • En el campo Argumentos, proporcione la opción de línea de comandos que utiliza el explorador para abrirse en el modo incógnito o privado. Algunos exploradores requieren la dirección URL de la aplicación.
      • Microsoft Edge: Use -inprivate.
      • Google Chrome: Use --incognito --new-window {URL}, donde el marcador de posición {URL} es la dirección URL que se va a abrir (por ejemplo, https://localhost:5001).
      • Mozilla Firefox: Use -private -url {URL}, donde el marcador de posición {URL} es la dirección URL que se va a abrir (por ejemplo, https://localhost:5001).
    • Proporcione un nombre en el campo Nombre descriptivo. Por ejemplo: Firefox Auth Testing.
    • Seleccione el botón Aceptar.
    • Para evitar tener que seleccionar el perfil de explorador para cada iteración de pruebas con una aplicación, establezca el perfil como predeterminado con el botón Establecer como predeterminado.
    • Asegúrese de que el explorador se cierra mediante el IDE siempre que se produzca cualquier cambio en la aplicación, el usuario de prueba o la configuración del proveedor.

Actualizaciones de aplicaciones

Una aplicación en funcionamiento deja de ejecutarse inmediatamente después de actualizar el SDK de .NET Core en la máquina de desarrollo o de cambiar las versiones del paquete en la aplicación. En algunos casos, los paquetes incoherentes pueden interrumpir una aplicación al realizar actualizaciones importantes. La mayoría de estos problemas puede corregirse siguiendo estas instrucciones:

  1. Borre las memorias caché del paquete NuGet del sistema local ejecutando dotnet nuget locals all --clear desde un shell de comandos.
  2. Elimine las carpetas bin y obj del proyecto.
  3. Restaure el proyecto y vuelva a compilarlo.
  4. Elimine todos los archivos de la carpeta de implementación del servidor antes de volver a implementar la aplicación.

Nota

No se pueden usar versiones de paquetes que no sean compatibles con la plataforma de destino de la aplicación. Para obtener información sobre un paquete, use la galería de NuGet o el explorador de paquetes FuGet.

Inspección de las notificaciones del usuario

Para solucionar problemas con las notificaciones de usuario, el componente UserClaims siguiente se puede usar directamente en aplicaciones o servir como base para una mayor personalización.

UserClaims.razor:

@page "/user-claims"
@using System.Security.Claims
@attribute [Authorize]

<PageTitle>User Claims</PageTitle>

<h1>User Claims</h1>

**Name**: @AuthenticatedUser?.Identity?.Name

<h2>Claims</h2>

@foreach (var claim in AuthenticatedUser?.Claims ?? Array.Empty<Claim>())
{
    <p class="claim">@(claim.Type): @claim.Value</p>
}

@code {
    [CascadingParameter]
    private Task<AuthenticationState>? AuthenticationState { get; set; }

    public ClaimsPrincipal? AuthenticatedUser { get; set; }

    protected override async Task OnInitializedAsync()
    {
        if (AuthenticationState is not null)
        {
            var state = await AuthenticationState;
            AuthenticatedUser = state.User;
        }
    }
}

Recursos adicionales