Protección de una aplicación hospedada Blazor WebAssembly de ASP.NET Core con Identity Server

En este artículo se explica cómo crear una solución Blazor WebAssembly hospedada que usa Duende Identity Server para autenticar usuarios y llamadas API.

Importante

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

Nota:

Para configurar una aplicación Blazor WebAssembly independiente o hospedada para usar una instancia de Identity Server externa existente, siga las instrucciones de Protección de una aplicación independiente Blazor WebAssembly de ASP.NET Core con la biblioteca de autenticación.

Para obtener cobertura de escenarios de seguridad adicionales después de leer este artículo, consulte los escenarios de seguridad adicionales de ASP.NET Core Blazor WebAssembly.

Tutorial

En las subsecciones del tutorial se explica cómo:

  • Crear la aplicación Blazor
  • Ejecutar la aplicación

Creación de una aplicación Blazor

Para crear un nuevo proyecto de Blazor WebAssembly con un mecanismo de autenticación:

  1. Cree un nuevo proyecto.

  2. Elija la plantilla Aplicación de Blazor WebAssembly . Seleccione Siguiente.

  3. Proporcione un nombre de proyecto sin usar guiones. Confirme que la Ubicación es correcta. Seleccione Next (Siguiente).

    Evite usar guiones (-) en el nombre del proyecto que interrumpen la formación del identificador de aplicación de OIDC. La lógica de la plantilla de proyecto Blazor WebAssembly usa el nombre del proyecto para un identificador de aplicación OIDC en la configuración de la solución y los guiones no se permiten en un identificador de aplicación OIDC. El uso de las mayúsculas y minúsculas Pascal (BlazorSample) o los guiones bajos (Blazor_Sample) son alternativas aceptables.

  4. En el cuadro de diálogo Información adicional, seleccione Cuentas individuales como Tipo de autenticación para almacenar usuarios dentro de la aplicación mediante el sistema Identity de ASP.NET Core.

  5. Active la casilla ASP.NET Core hospedado.

  6. Seleccione el botón Crear para crear la aplicación.

Ejecutar la aplicación

Ejecute la aplicación desde el proyecto Server. Al usar Visual Studio, puede hacer lo siguiente:

  • Seleccione la flecha desplegable situada junto al botón Ejecutar. Abra Configurar proyectos de inicio en la lista desplegable. Seleccione la opción Proyecto de inicio único. Confirme o cambie el proyecto de inicio al proyecto Server.

  • Confirme que el proyecto Server está resaltado en el Explorador de soluciones antes de iniciar la aplicación con cualquiera de los enfoques siguientes:

    • Haga clic en el botón Ejecutar.
    • En el menú, seleccione Depurar>Iniciar depuración.
    • Presione F5.
  • En un shell de comandos, vaya a la carpeta del proyecto Server de la solución. Ejecute el comando dotnet run.

Partes de la solución

En esta sección se describen las partes de una solución generada a partir de la plantilla de proyecto Blazor WebAssembly y se describe cómo se configuran los proyectos Client y Server de la solución como referencia. No hay ninguna guía específica que se debe seguir en esta sección para una aplicación operativa básica si creó la aplicación con las instrucciones de la sección Tutorial. Las instrucciones de esta sección son útiles para actualizar una aplicación a fin de autenticar y autorizar a los usuarios. Pero un enfoque alternativo para actualizar una aplicación consiste en crear una aplicación a partir de las instrucciones de la sección Tutorial y mover los componentes, clases y recursos de la aplicación a la nueva aplicación.

Servicios de aplicaciones de Server

Esta sección atañe a la aplicación Server de la solución.

Los servicios siguientes están registrados.

  • En el archivo Program:

    • Identity de Entity Framework Core y ASP.NET Core:

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

      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 Identity Server:

      builder.Services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • En Startup.ConfigureServices de Startup.cs:

    • Identity de Entity Framework Core y ASP.NET Core:

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

      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 Identity Server:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      

Nota

Cuando se registra un único esquema de autenticación, se usa automáticamente como esquema predeterminado de la aplicación y no es necesario indicar el esquema a AddAuthentication o a través de AuthenticationOptions. Para obtener más información, consulte:Información general sobre la autenticación de ASP.NET Core y el anuncio de ASP.NET Core (aspnet/Announcements #490).

  • En el archivo Program:
  • En Startup.Configure de Startup.cs:
  • El middleware Identity Server expone los puntos de conexión de OpenID Connect (OIDC):

    app.UseIdentityServer();
    
  • 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 Authorization habilita capacidades de autorización:

    app.UseAuthorization();
    

Autorización de API

Esta sección atañe a la aplicación Server de la solución.

El método auxiliar AddApiAuthorization configura Identity Server en escenarios de ASP.NET Core. Identity Server es un marco eficaz y extensible que sirve para abordar los problemas de seguridad de las aplicaciones. Identity Server expone las complejidades innecesarias en los escenarios más comunes. En consecuencia, se proporciona un conjunto de convenciones y opciones de configuración que consideramos un buen punto de partida. Cuando sus necesidades de autenticación cambien, tendrá a su disposición toda la eficacia de Identity Server para personalizar la autenticación para adaptarse a los requisitos de una aplicación.

Adición de un controlador de autenticación para una API que coexista con Identity Server

Esta sección atañe a la aplicación Server de la solución.

El método auxiliar AddIdentityServerJwt 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 subruta en el espacio de dirección URL de Identity en /Identity. JwtBearerHandler se encarga de todas las demás solicitudes. Este método también hace lo siguiente:

  • Registra un recurso de API con Identity Server con un ámbito predeterminado de {PROJECT NAME}API, donde el marcador de posición {PROJECT NAME} es el nombre del proyecto en la creación de la aplicación.
  • Configura el middleware de token de portador de JWT para validar los tokens emitidos por Identity Server para la aplicación.

Controlador de previsión meteorológica

Esta sección atañe a la aplicación Server de la solución.

En WeatherForecastController (Controllers/WeatherForecastController.cs), el atributo [Authorize] se aplica a la clase. Este atributo señala que, para acceder al recurso, el usuario debe estar autorizado en función de la directiva predeterminada. La directiva de autorización predeterminada está configurada para usar el esquema de autenticación predeterminado, que AddIdentityServerJwt configura. El método auxiliar configura JwtBearerHandler como el controlador predeterminado de las solicitudes a la aplicación.

Contexto de base de datos de aplicación

Esta sección atañe a la aplicación Server de la solución.

En ApplicationDbContext (Data/ApplicationDbContext.cs), DbContext extiende ApiAuthorizationDbContext<TUser> para incluir el esquema de Identity Server. La clase ApiAuthorizationDbContext<TUser> se deriva de la clase IdentityDbContext.

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

Controlador de configuración de OIDC

Esta sección atañe a la aplicación Server de la solución.

En OidcConfigurationController (Controllers/OidcConfigurationController.cs), el punto de conexión del cliente se aprovisiona para proporcionar parámetros de OIDC.

Configuración de la aplicación

Esta sección atañe a la aplicación Server de la solución.

En el archivo de configuración de la aplicación (appsettings.json), en la raíz del proyecto, la sección IdentityServer describe la lista de clientes configurados. En el siguiente ejemplo hay un solo cliente, El nombre del cliente corresponde al nombre del ensamblado de la aplicación Client 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. El perfil se usa internamente para controlar las convenciones que simplifican el proceso de configuración del servidor.

"IdentityServer": {
  "Clients": {
    "{ASSEMBLY NAME}": {
      "Profile": "IdentityServerSPA"
    }
  }
}

El marcador de posición {ASSEMBLY NAME} es el nombre del ensamblado de la aplicación Client (por ejemplo, BlazorSample.Client).

Paquete de autenticación

Esta sección atañe a la aplicación Client de la solución.

Cuando una aplicación se crea para usar cuentas de usuario individuales (Individual), dicha aplicación recibe automáticamente una referencia de paquete del paquete Microsoft.AspNetCore.Components.WebAssembly.Authentication. El paquete proporciona un conjunto de primitivas que ayudan a la aplicación a autenticar usuarios y a obtener tokens para llamar a API protegidas.

Si agrega autenticación a una aplicación, agregue el paquete Microsoft.AspNetCore.Components.WebAssembly.Authentication manualmente a la aplicación:

Nota

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulte los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (NuGet documentación). Confirme las versiones correctas del paquete en NuGet.org.

Configuración de HttpClient

Esta sección atañe a la aplicación Client de la solución.

En el archivo Program, una instancia de HttpClient con nombre está configurada para administrar instancias de HttpClient que incluyan tokens de acceso al hacer solicitudes a la API del servidor. De forma predeterminada, en la creación de la solución, el elemento denominado HttpClient es {PROJECT NAME}.ServerAPI, donde el marcador de posición {PROJECT NAME} es el nombre del proyecto.

builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", 
        client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
    .CreateClient("{PROJECT NAME}.ServerAPI"));

El marcador de posición {PROJECT NAME} es el nombre del proyecto en la creación de la solución. Por ejemplo, proporcionar un nombre de proyecto de BlazorSample genera un elemento denominado HttpClient de BlazorSample.ServerAPI.

Nota:

Si va a configurar una aplicación Blazor WebAssembly para usar una instancia de Identity Server existente que no forma parte de una solución hospedada por Blazor, cambie el registro de la dirección base HttpClient de IWebAssemblyHostEnvironment.BaseAddress (builder.HostEnvironment.BaseAddress) a la dirección URL del punto de conexión de autorización de API de la aplicación de servidor.

Compatibilidad con autorización de API

Esta sección atañe a la aplicación Client de la solución.

La compatibilidad para autenticar usuarios se incluye en el contenedor de servicios con el método de extensión proporcionado dentro del paquete Microsoft.AspNetCore.Components.WebAssembly.Authentication. Este método configura los servicios necesarios para que la aplicación interactúe con el sistema de autorización existente.

builder.Services.AddApiAuthorization();

La configuración de la aplicación se carga por convención de forma predeterminada desde _configuration/{client-id}. Por convención, el identificador de cliente se establece en el nombre de ensamblado de la aplicación. Esta dirección URL se puede cambiar para que apunte a un punto de conexión aparte llamando a la sobrecarga con opciones.

Archivo Imports

Esta sección atañe a la aplicación Client de la solución.

El espacio de nombres Microsoft.AspNetCore.Components.Authorization está disponible en toda la aplicación a través del archivo _Imports.razor:

@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using {APPLICATION ASSEMBLY}
@using {APPLICATION ASSEMBLY}.Shared

Página Index

Esta sección atañe a la aplicación Client de la solución.

La página de índice (wwwroot/index.html) incluye un script que define AuthenticationService en JavaScript. AuthenticationService controla los detalles de bajo nivel del protocolo OIDC. La aplicación llama internamente a métodos definidos en el script para realizar las operaciones de autenticación.

<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>

Componente de App

Esta sección atañe a la aplicación Client de la solución.

El componente App (App.razor) es similar al componente App que se encuentra en las aplicaciones Blazor Server:

  • El componente CascadingAuthenticationState administra la exposición de AuthenticationState al resto de la aplicación.
  • El componente AuthorizeRouteView se asegura de que el usuario actual está autorizado para tener acceso a una página determinada o, de lo contrario, representa el componente RedirectToLogin.
  • El componente RedirectToLogin administra el redireccionamiento de usuarios no autorizados a la página de inicio de sesión.

Debido a los cambios del marco en las versiones de ASP.NET Core, el marcado de Razor del componente App (App.razor) no se muestra en esta sección. Para inspeccionar el marcado del componente para una versión determinada, use cualquiera de los enfoques siguientes:

  • Cree una aplicación aprovisionada para la autenticación a partir de la plantilla de proyecto Blazor WebAssembly predeterminada para la versión de ASP.NET Core que va a usar. Inspeccione el componente App (App.razor) en la aplicación generada.

  • Inspeccione el componente App (App.razor) en el origen de referencia. Seleccione la versión del selector de rama y busque el componente en la carpeta ProjectTemplates del repositorio porque se ha movido a lo largo de los años.

    Nota:

    Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Componente de RedirectToLogin

Esta sección atañe a la aplicación Client de la solución.

El componente RedirectToLogin (RedirectToLogin.razor):

  • Administra el redireccionamiento de usuarios no autorizados a la página de inicio de sesión.
  • La dirección URL actual a la que el usuario intenta tener acceso se conserva para que pueda volver a esa página si la autenticación se realiza correctamente con:

Inspeccione el componente RedirectToLogin en el origen de referencia. La ubicación del componente ha cambiado con el tiempo, use las herramientas de búsqueda de GitHub para localizar el componente.

Nota:

Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Componente de LoginDisplay

Esta sección atañe a la aplicación Client de la solución.

El componente LoginDisplay (LoginDisplay.razor) se representa en el componente MainLayout (MainLayout.razor) y administra los siguientes comportamientos:

  • En el caso de los usuarios autenticados:
    • Muestra el nombre de usuario actual.
    • Proporciona un vínculo a la página de perfil de usuario en Identity de ASP.NET Core.
    • Proporciona un botón para cerrar la sesión de la aplicación.
  • En el caso de los usuarios anónimos:
    • Ofrece la opción de registrarse.
    • Ofrece la opción de iniciar sesión.

Debido a los cambios de marco en las versiones de ASP.NET Core, el marcado Razor del componente LoginDisplay no se muestra en esta sección. Para inspeccionar el marcado del componente para una versión determinada, use cualquiera de los enfoques siguientes:

  • Cree una aplicación aprovisionada para la autenticación a partir de la plantilla de proyecto Blazor WebAssembly predeterminada para la versión de ASP.NET Core que va a usar. Inspeccione el componente LoginDisplay en la aplicación generada.

  • Inspeccione el componente LoginDisplay en el origen de referencia. La ubicación del componente ha cambiado con el tiempo, use las herramientas de búsqueda de GitHub para localizar el componente. Se usa el contenido con plantilla para Hosted igual a true.

    Nota

    Los vínculos de la documentación al origen de referencia de .NET cargan normalmente la rama predeterminada del repositorio, que representa el desarrollo actual para la próxima versión de .NET. Para seleccionar una etiqueta de una versión específica, use la lista desplegable Cambiar ramas o etiquetas. Para obtener más información, vea Procedimientos para seleccionar una etiqueta de versión de código fuente de ASP.NET Core (dotnet/AspNetCore.Docs #26205).

Componente de Authentication

Esta sección atañe a la aplicación Client de la solución.

La página generada por el componente Authentication (Pages/Authentication.razor) define las rutas necesarias para controlar diferentes fases de autenticación.

El componente RemoteAuthenticatorView:

@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication

<RemoteAuthenticatorView Action="Action" />

@code {
    [Parameter]
    public string? Action { get; set; }
}

Nota:

Los tipos de referencia que admiten un valor NULL (NRT) y el análisis estático de estado NULL del compilador de .NET se admiten en ASP.NET Core en .NET 6 o posterior. Antes de la versión de ASP.NET Core en .NET 6, el tipo string aparece sin la designación de tipo NULL (?).

Componente de FetchData

Esta sección atañe a la aplicación Client de la solución.

El componente FetchData muestra:

  • Cómo aprovisionar un token de acceso.
  • Cómo usar el token de acceso para llamar a una API de recursos protegidos en la aplicación Server.

La directiva @attribute [Authorize] indica al sistema de autorización de Blazor WebAssembly que el usuario debe estar autorizado para poder visitar este componente. La presencia del atributo en la aplicación Client no impide que se llame a la API en el servidor sin credenciales adecuadas. La aplicación Server debe usar también [Authorize] en los puntos de conexión adecuados para protegerlos correctamente.

IAccessTokenProvider.RequestAccessToken se encarga de solicitar un token de acceso que se puede agregar a la solicitud para llamar a la API. Si el token se almacena en caché o el servicio puede aprovisionar un nuevo token de acceso sin la interacción del usuario, la solicitud de token se realiza correctamente. De lo contrario, se produce un error en la solicitud de token con una AccessTokenNotAvailableException, que se detecta en una instrucción try-catch.

Para obtener el token real que se va a incluir en la solicitud, la aplicación debe comprobar que la solicitud se ha realizado correctamente mediante una llamada a tokenResult.TryGetToken(out var token).

Si la solicitud se ha realizado correctamente, la variable de token se rellena con el token de acceso. La propiedad AccessToken.Value del token expone la cadena literal que se va a incluir en el encabezado de solicitud Authorization.

Si se ha producido un error en la solicitud porque no se ha podido aprovisionar el token sin interacción del usuario:

  • ASP.NET Core en .NET 7 o posterior: la aplicación se dirige a AccessTokenResult.InteractiveRequestUrl usando el valor AccessTokenResult.InteractionOptions especificado para permitir la actualización del token de acceso.
  • ASP.NET Core en .NET 6 o una versión anterior: el resultado del token contiene una URL de redireccionamiento. Al navegar a esta dirección URL, el usuario se dirige a la página de inicio de sesión y vuelve a la página actual si se autentica correctamente.
@page "/fetchdata"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using {APP NAMESPACE}.Shared
@attribute [Authorize]
@inject HttpClient Http

...

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

Azure App Service en Linux

Al realizar una implementación en Azure App Service en Linux, especifique el emisor de forma explícita. Para más información, consulte Uso de Identity para proteger un back-end de API web para SPA.

Notificaciones name y role con autorización de API

Fábrica de usuario personalizada

En la aplicación Client , cree una fábrica de usuario personalizada. Identity Server envía varios roles como una matriz JSON en una sola notificación role. Se envía un único rol como un valor de cadena en la notificación. La fábrica crea una notificación role individual por cada uno de los roles del usuario.

CustomUserFactory.cs:

using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity is not null && user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = 
                    account.AdditionalProperties[identity.RoleClaimType];

                if (options.RoleClaim is not null && rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            var roleValue = role.GetString();

                            if (!string.IsNullOrEmpty(roleValue))
                            {
                                identity.AddClaim(
                                  new Claim(options.RoleClaim, roleValue));
                            }

                        }
                    }
                    else
                    {
                        var roleValue = roles.GetString();

                        if (!string.IsNullOrEmpty(roleValue))
                        {
                            identity.AddClaim(
                              new Claim(options.RoleClaim, roleValue));
                        }
                    }
                }
            }
        }

        return user;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;

public class CustomUserFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor)
        : base(accessor)
    {
    }

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        RemoteUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            var identity = (ClaimsIdentity)user.Identity;
            var roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();

            if (roleClaims.Any())
            {
                foreach (var existingClaim in roleClaims)
                {
                    identity.RemoveClaim(existingClaim);
                }

                var rolesElem = account.AdditionalProperties[identity.RoleClaimType];

                if (rolesElem is JsonElement roles)
                {
                    if (roles.ValueKind == JsonValueKind.Array)
                    {
                        foreach (var role in roles.EnumerateArray())
                        {
                            identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
                        }
                    }
                    else
                    {
                        identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
                    }
                }
            }
        }

        return user;
    }
}

En la aplicación Client, registre la fábrica en el archivo Program:

builder.Services.AddApiAuthorization()
    .AddAccountClaimsPrincipalFactory<CustomUserFactory>();

En la aplicación Server, se llama a AddRoles, en el generador de Identity que agrega servicios relativos a roles:

En el archivo Program:

using Microsoft.AspNetCore.Identity;

...

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

En Startup.cs:

using Microsoft.AspNetCore.Identity;

...

services.AddDefaultIdentity<ApplicationUser>(options => 
    options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Configuración de Identity Server

Siga uno de estos procedimientos:

Opciones de autorización de API

En la aplicación Server :

  • Configure Identity Server para colocar las notificaciones name y role en el token de identificador y el token de acceso.
  • Evite la asignación predeterminada de roles en el controlador de token JWT.

En el archivo Program:

using System.IdentityModel.Tokens.Jwt;

...

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

En Startup.cs:

using System.IdentityModel.Tokens.Jwt;
using System.Linq;

...

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
        options.IdentityResources["openid"].UserClaims.Add("name");
        options.ApiResources.Single().UserClaims.Add("name");
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
    });

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Servicio de perfil

En la aplicación Server , cree una implementación ProfileService.

ProfileService.cs:

using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}
using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
using System.Threading.Tasks;

public class ProfileService : IProfileService
{
    public ProfileService()
    {
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
        context.IssuedClaims.AddRange(nameClaim);

        var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
        context.IssuedClaims.AddRange(roleClaims);

        await Task.CompletedTask;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        await Task.CompletedTask;
    }
}

En la aplicación Server, registre el servicio de perfil en el archivo Program:

using Duende.IdentityServer.Services;

...

builder.Services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

En la aplicación Server, registre el servicio de perfil en Startup.ConfigureServices de Startup.cs:

using IdentityServer4.Services;

...

services.AddTransient<IProfileService, ProfileService>();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

Uso de mecanismos de autorización

En la aplicación Client , los métodos de autorización de componentes son funcionales en este momento. Cualquiera de los mecanismos de autorización de los componentes puede usar un rol para autorizar al usuario:

User.Identity.Name se rellena en la aplicación Client con el nombre de usuario del usuario, que suele ser su dirección de correo electrónico de inicio de sesión.

UserManager y SignInManager

Establezca el tipo de notificaciones de identificador de usuario cuando una aplicación de servidor requiera:

En Program.cs para ASP.NET Core en .NET 6 o posterior:

using System.Security.Claims;

...

builder.Services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

En Startup.ConfigureServices para versiones de ASP.NET Core anteriores a 6.0:

using System.Security.Claims;

...

services.Configure<IdentityOptions>(options => 
    options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);

El elemento WeatherForecastController siguiente registra la propiedad UserName cuando se llama al método Get:

Nota

En el ejemplo siguiente se usa un espacio de nombres con ámbito de archivo, que es una característica de C# 10 o posterior (.NET 6 o posterior).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using BlazorSample.Server.Models;
using BlazorSample.Shared;

namespace BlazorSample.Server.Controllers;

[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly UserManager<ApplicationUser> userManager;

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", 
        "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, 
        UserManager<ApplicationUser> userManager)
    {
        this.logger = logger;
        this.userManager = userManager;
    }

    [HttpGet]
    public async Task<IEnumerable<WeatherForecast>> Get()
    {
        var rng = new Random();

        var user = await userManager.GetUserAsync(User);

        if (user != null)
        {
            logger.LogInformation("User.Identity.Name: {UserIdentityName}", user.UserName);
        }

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

En el ejemplo anterior:

  • El espacio de nombres del proyecto de Server es BlazorSample.Server.
  • El espacio de nombres del proyecto de Shared es BlazorSample.Shared.

Host en Azure App Service con un dominio personalizado y un certificado

En la guía siguiente se explica:

  • Cómo implementar una aplicación de Blazor WebAssembly hospedada con Identity Server para Azure App Service con un dominio personalizado.
  • Cómo crear y usar un certificado TLS para la comunicación del protocolo HTTPS con exploradores. Aunque la guía se centra en el uso del certificado con un dominio personalizado, la guía se aplica igualmente al uso de un dominio predeterminado de aplicaciones de Azure, por ejemplo contoso.azurewebsites.net.

En este escenario de hospedaje, no use el mismo certificado para la clave de firma de tokens de Identity Server y la comunicación segura HTTPS del sitio con exploradores:

  • Usar certificados diferentes para estos dos requisitos es una buena práctica de seguridad porque aísla claves privadas para cada propósito.
  • Los certificados TLS para la comunicación con los exploradores se administran de forma independiente sin afectar a la firma de tokens de Identity Server.
  • Cuando Azure Key Vault proporciona un certificado a una aplicación de App Service para el enlace de dominio personalizado, Identity Server no puede obtener el mismo certificado de Azure Key Vault para la firma de tokens. Aunque es posible configurar Identity Server para que use el mismo certificado TLS desde una ruta de acceso física, la colocación de los certificados de seguridad en el control de código fuente es una práctica inadecuada y debe evitarse en la mayoría de los casos.

En la siguiente guía, se crea un certificado autofirmado en Azure Key Vault únicamente para la firma de tokens de Identity Server. La configuración de Identity Server usa el certificado del almacén de claves mediante el almacén de certificados CurrentUser>My de la aplicación. Otros certificados que se usan para el tráfico HTTPS con dominios personalizados se crean y se configuran de forma independiente del certificado de firma de Identity Server.

Para configurar una aplicación, Azure App Service y Azure Key Vault para que hospeden con un dominio personalizado y HTTPS:

  1. Cree un plan de App Service con un nivel de plan de Basic B1 o superior. App Service requiere un nivel de servicio Basic B1 o superior para usar dominios personalizados.

  2. Cree un certificado PFX para la comunicación segura del explorador del sitio (protocolo HTTPS) con un nombre común del nombre de dominio completo (FQDN) del sitio que controla la organización (por ejemplo, www.contoso.com). Cree el certificado con:

    • Usos de las claves
      • Validación de firma digital (digitalSignature)
      • Cifrado de claves (keyEncipherment)
    • Usos mejorados/extendidos de las claves
      • Autenticación de cliente (1.3.6.1.5.5.7.3.2)
      • Autenticación del servidor (1.3.6.1.5.5.7.3.1)

    Para crear el certificado, use uno de los métodos siguientes, o cualquier otra herramienta o servicio en línea adecuado:

    Anote la contraseña, que se usará más adelante para importar el certificado en Azure Key Vault.

    Para más información sobre los certificados de Azure Key Vault, consulte Azure Key Vault: Certificados.

  3. Cree una nueva instancia de Azure Key Vault o use un almacén de claves existente en su suscripción de Azure.

  4. En el área Certificados del almacén de claves, importe el certificado del sitio PFX. Registre la huella digital del certificado, que se usará en la configuración de la aplicación más adelante.

  5. En Azure Key Vault, genere un nuevo certificado autofirmado para la firma de tokens de Identity Server. Asigne al certificado un Nombre de certificado y un Firmante. El Firmante se especifica como CN={COMMON NAME}, donde el marcador de posición {COMMON NAME} es el nombre común del certificado. El nombre común puede ser cualquier cadena alfanumérica. Por ejemplo, CN=IdentityServerSigning es un Firmante válido de certificado. En Configuración de directiva avanzada>Configuración de directiva avanzada, use la configuración predeterminada. Registre la huella digital del certificado, que se usará en la configuración de la aplicación más adelante.

  6. Vaya a Azure App Service en Azure Portal y cree un nuevo App Service con la siguiente configuración:

    • Publicar establecido en Code.
    • Pila en tiempo de ejecución establecido en el tiempo de ejecución de la aplicación.
    • Para SKU y tamaño, confirme que el nivel de App Service sea Basic B1 o superior. App Service requiere un nivel de servicio Basic B1 o superior para usar dominios personalizados.
  7. Después de que Azure cree App Service, abra la Configuración de la aplicación y agregue una nueva configuración de aplicación que especifique las huellas digitales del certificado que se registraron anteriormente. La clave de configuración de la aplicación es WEBSITE_LOAD_CERTIFICATES. Separe las huellas digitales de certificado en el valor de configuración de la aplicación con una coma, como se muestra en el ejemplo siguiente:

    • Clave: WEBSITE_LOAD_CERTIFICATES
    • Valor: 57443A552A46DB...D55E28D412B943565,29F43A772CB6AF...1D04F0C67F85FB0B1

    En Azure Portal, guardar la configuración de la aplicación es un proceso de dos pasos: Guarde la configuración de clave-valor de WEBSITE_LOAD_CERTIFICATES y, después, seleccione el botón Guardar, en la parte superior de la hoja.

  8. Seleccione la configuración de TLS/SSL de la aplicación. Seleccione Certificados de clave privada (.pfx) . Use el proceso Importar el certificado de Key Vault. Use el proceso dos veces para importar el certificado del sitio para la comunicación HTTPS y el certificado autofirmado del sitio para la firma de tokens de Identity Server.

  9. Vaya a la hoja Dominios personalizados. En el sitio web del registrador de dominios, use la dirección IP y el id. de verificación del dominio personalizado para configurar el dominio. Una configuración de dominio típica incluye:

    • Un registro A con un host de @ y un valor de la dirección IP de Azure Portal.
    • Un registro TXT con un host de asuid y el valor del id. de verificación generado por Azure y proporcionado por Azure Portal.

    Asegúrese de guardar correctamente los cambios en el sitio web del registrador de dominios. Algunos sitios web del registrador requieren un proceso de dos pasos para guardar los registros de dominio: Uno o varios registros se guardan individualmente seguidos de la actualización del registro del dominio con un botón independiente.

  10. Vuelva a la hoja Dominios personalizados en Azure Portal. Seleccione Agregar dominio personalizado. Seleccione la opción Registro A. Proporcione el dominio y seleccione Validar. Si los registros de dominio son correctos y están propagados por Internet, el portal le permitirá seleccionar el botón Agregar dominio personalizado.

    Los cambios en el registro de dominio pueden tardar unos días en propagarse entre los servidores de nombres de dominio (DNS) de Internet después de que el registrador de dominios los procese. Si los registros de dominio no se actualizan en un plazo de tres días laborables, confirme que los registros se han establecido correctamente con el registrador de dominios y póngase en contacto con el servicio de soporte técnico al cliente.

  11. En la hoja Dominios personalizados, el ESTADO SSL del dominio está marcado como Not Secure. Seleccione el vínculo Agregar enlaces. Seleccione el certificado HTTPS del sitio en el almacén de claves para el enlace de dominio personalizado.

  12. En Visual Studio, abra el archivo de configuración de la aplicación del proyecto Server (appsettings.json o appsettings.Production.json). En la configuración de Identity Server, agregue la siguiente sección de Key. Especifique el Firmante del certificado autofirmado para la clave Name. En el ejemplo siguiente, el nombre común del certificado asignado en el almacén de claves es IdentityServerSigning, lo que produce un Firmante de CN=IdentityServerSigning:

    "IdentityServer": {
    
      ...
    
      "Key": {
        "Type": "Store",
        "StoreName": "My",
        "StoreLocation": "CurrentUser",
        "Name": "CN=IdentityServerSigning"
      }
    },
    
  13. En Visual Studio, cree un perfil de publicación de Azure App Service para el proyecto Server. En la barra de menús, seleccione: Compilar>Publicar>Nuevo>Azure>Azure App Service (Windows o Linux). Cuando Visual Studio se conecta a una suscripción de Azure, puede establecer la Vista de recursos de Azure por Tipo de recurso. Navegue dentro de la lista Aplicación web para buscar la instancia de App Service de la aplicación y selecciónela. Seleccione Finalizar.

  14. Cuando Visual Studio vuelve a la ventana Publicar, se detectan automáticamente el almacén de claves y las dependencias del servicio de base de datos SQL Server.

    No se requiere ningún cambio de configuración en la configuración predeterminada para el servicio del almacén de claves.

    Con fines de prueba, la base de datos SQLite local de una aplicación, que está configurada de forma predeterminada por la plantilla de Blazor, puede implementarse con la aplicación sin configuración adicional. La configuración de una base de datos diferente en producción para Identity Server está fuera del ámbito de este artículo. Para obtener más información, consulte los recursos de base de datos en los siguientes recursos:

  15. Seleccione el enlace Editar debajo del nombre del perfil de implementación en la parte superior de la ventana. Cambie la dirección URL de destino a la dirección URL de dominio personalizada del sitio (por ejemplo, https://www.contoso.com). Guarde la configuración

  16. Publique la aplicación. Visual Studio abre una ventana del explorador y solicita el sitio en su dominio personalizado.

La documentación de Azure contiene detalles adicionales sobre el uso de servicios de Azure y de dominios personalizados con enlace de TLS en App Service, incluida información sobre el uso de registros CNAME en lugar de registros A. Para obtener más información, vea los siguientes recursos:

Se recomienda usar una nueva ventana del explorador en modo privado (por ejemplo, el modo InPrivate de Microsoft Edge o el modo incógnito de Google Chrome) para cada prueba de aplicación que se ejecute después de un cambio en la aplicación, la configuración de la aplicación o los servicios de Azure en Azure Portal. Los elementos cookie persistentes de una serie de pruebas anterior pueden dar errores de autenticación o autorización al probar el sitio, incluso aunque la configuración del sitio sea correcta. Para obtener más información sobre cómo configurar Visual Studio para abrir una nueva ventana de explorador en modo privado para cada serie de pruebas, consulte la sección Cookie y datos del sitio.

Cuando se cambia la configuración de App Service en Azure Portal, las actualizaciones suelen surtir efecto rápidamente, pero no son instantáneas. A veces, debe esperar un breve período para que App Service se reinicie y se aplique un cambio de configuración.

Si va a solucionar un problema de carga de certificados de firma de clave de Identity Server, ejecute el siguiente comando en un shell de comandos Kudu de PowerShell en Azure Portal. El comando proporciona una lista de certificados a los que la aplicación puede tener acceso desde el almacén de certificados de CurrentUser>My. La salida incluye firmantes de certificados y huellas digitales útiles al depurar una aplicación:

Get-ChildItem -path Cert:\CurrentUser\My -Recurse | Format-List DnsNameList, Subject, Thumbprint, EnhancedKeyUsageList

Solución de problemas

Registro

Para habilitar el registro de depuración o rastreo para la autenticación Blazor WebAssembly, consulte la sección Registro de autenticación del lado del cliente de Registro de ASP.NET Core Blazor con el selector de versión del artículo establecido en ASP.NET Core 7.0 o posterior.

Errores comunes

  • Error de configuración de la aplicación o del proveedor de Identity (IP)

    Los errores más comunes se deben a una configuración incorrecta. Estos son algunos ejemplos:

    • En función de los requisitos del escenario, la disponibilidad o no de una autoridad, una instancia, un identificador o dominio de inquilino, un identificador de cliente o un URI de redireccionamiento, o bien que estos no elementos no sean correctos, impide a una aplicación autenticar clientes.
    • Los ámbitos de solicitud incorrectos impiden a los clientes acceder a los puntos de conexión de la API web del servidor.
    • Faltan permisos de la API de servidor o estos son incorrectos, lo cual impide a los clientes acceder a los puntos de conexión de API web.
    • Ejecutar la aplicación en un puerto diferente al configurado en el URI de redirección del registro de la aplicación de la IP. Tenga en cuenta que no se requiere un puerto para Microsoft Entra ID y una aplicación que se ejecute en una dirección de pruebas de desarrollo localhost, pero la configuración del puerto de la aplicación y el puerto en el que se ejecuta la aplicación deben coincidir para las direcciones que no sean localhost.

    En las secciones de configuración de la guía de este artículo se muestran ejemplos de la configuración correcta. Compruebe detenidamente cada sección del artículo en busca de la configuración de la aplicación y la de IP.

    Si la configuración parece correcta:

    • Analice los registros de la aplicación.

    • Examine el tráfico de red entre la aplicación cliente y la de servidor, o la dirección IP con las herramientas de desarrollo del explorador. A menudo, la aplicación de servidor o la dirección IP 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:

    • Para las versiones de Blazor en las que se utiliza un JStoken web (JWT) ON, descodifique el contenido del token utilizado para autenticar a un cliente o acceder a una API web del servidor, dependiendo de dónde se esté produciendo el problema. Para obtener más información, consulte Inspección del contenido de un JSON Web Token (JWT).

    El equipo de documentación responde a los comentarios y los errores en los artículos (abra una incidencia en la sección de comentarios de esta página), pero 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).

  • Cliente no autorizado para ME-ID

    Información: Error de autorización de Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]. No se cumplen estos requisitos: DenyAnonymousAuthorizationRequirement: se requiere un usuario autenticado.

    Error de devolución de llamada de inicio de sesión de ME-ID:

    • Error: unauthorized_client
    • Description (Descripción): AADB2C90058: The provided application is not configured to allow public clients.

    Para resolver el error:

    1. En Azure Portal, acceda al manifiesto de la aplicación.
    2. Establezca el atributo allowPublicClient en null o true.

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. Borre los elementos siguientes al realizar cambios en el código de la aplicación, cambios en la cuenta de usuario con el proveedor o cuando el proveedor modifique la configuración de la aplicación:

  • 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.

Ejecute la aplicación Server

Al realizar pruebas y solucionar problemas de una soluciónBlazor WebAssembly hospedada, asegúrese de ejecutar la aplicación desde el proyecto Server.

Inspección del usuario

El siguiente componente User se puede usar directamente en las aplicaciones, o bien servir como base para una mayor personalización.

User.razor:

@page "/user"
@attribute [Authorize]
@using System.Text.Json
@using System.Security.Claims
@inject IAccessTokenProvider AuthorizationService

<h1>@AuthenticatedUser?.Identity?.Name</h1>

<h2>Claims</h2>

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

<h2>Access token</h2>

<p id="access-token">@AccessToken?.Value</p>

<h2>Access token claims</h2>

@foreach (var claim in GetAccessTokenClaims())
{
    <p>@(claim.Key): @claim.Value.ToString()</p>
}

@if (AccessToken != null)
{
    <h2>Access token expires</h2>

    <p>Current time: <span id="current-time">@DateTimeOffset.Now</span></p>
    <p id="access-token-expires">@AccessToken.Expires</p>

    <h2>Access token granted scopes (as reported by the API)</h2>

    @foreach (var scope in AccessToken.GrantedScopes)
    {
        <p>Scope: @scope</p>
    }
}

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

    public ClaimsPrincipal AuthenticatedUser { get; set; }
    public AccessToken AccessToken { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        var state = await AuthenticationState;
        var accessTokenResult = await AuthorizationService.RequestAccessToken();

        if (!accessTokenResult.TryGetToken(out var token))
        {
            throw new InvalidOperationException(
                "Failed to provision the access token.");
        }

        AccessToken = token;

        AuthenticatedUser = state.User;
    }

    protected IDictionary<string, object> GetAccessTokenClaims()
    {
        if (AccessToken == null)
        {
            return new Dictionary<string, object>();
        }

        // header.payload.signature
        var payload = AccessToken.Value.Split(".")[1];
        var base64Payload = payload.Replace('-', '+').Replace('_', '/')
            .PadRight(payload.Length + (4 - payload.Length % 4) % 4, '=');

        return JsonSerializer.Deserialize<IDictionary<string, object>>(
            Convert.FromBase64String(base64Payload));
    }
}

Inspección del contenido de un JSON Web Token (JWT)

Para descodificar un JSON Web Token (JWT), use la herramienta jwt.ms de Microsoft. Los valores de la interfaz de usuario nunca salen del explorador.

Ejemplo de JWT codificado (se muestra una versión abreviada):

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1j ... bQdHBHGcQQRbW7Wmo6SWYG4V_bU55Ug_PW4pLPr20tTS8Ct7_uwy9DWrzCMzpD-EiwT5IjXwlGX3IXVjHIlX50IVIydBoPQtadvT7saKo1G5Jmutgq41o-dmz6-yBMKV2_nXA25Q

Ejemplo de JWT descodificado por la herramienta para una aplicación que se autentica en Azure AAD B2C:

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
  "exp": 1610059429,
  "nbf": 1610055829,
  "ver": "1.0",
  "iss": "https://mysiteb2c.b2clogin.com/5cc15ea8-a296-4aa3-97e4-226dcc9ad298/v2.0/",
  "sub": "5ee963fb-24d6-4d72-a1b6-889c6e2c7438",
  "aud": "70bde375-fce3-4b82-984a-b247d823a03f",
  "nonce": "b2641f54-8dc4-42ca-97ea-7f12ff4af871",
  "iat": 1610055829,
  "auth_time": 1610055822,
  "idp": "idp.com",
  "tfp": "B2C_1_signupsignin"
}.[Signature]

Recursos adicionales