Share via


Proteger o ASP.NET Core Blazor WebAssembly com a ASP.NET Core Identity

Aplicativos autônomos do Blazor WebAssembly podem ser protegidos com a ASP.NET Core Identity seguindo as diretrizes deste artigo.

Pontos de extremidade para registrar-se, fazer logon e fazer logout

Em vez de usar a interface do usuário padrão fornecida pelo ASP.NET Core Identity para aplicativos SPA e Blazor, que se baseia no Razor Pages, chame MapIdentityApi em uma API de back-end para adicionar pontos de extremidade da API JSON para registrar e fazer logon de usuários com o ASP.NET Core Identity. Os pontos de extremidade de API Identity também dão suporte a recursos avançados, como autenticação de dois fatores e verificação de email.

No cliente, chame o ponto de extremidade /register para registrar um usuário com seu endereço de email e senha:

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

No cliente, faça logon em um usuário com autenticação cookie usando o ponto de extremidade /login com a cadeia de caracteres de consulta useCookies definida como true:

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

A API do servidor back-end estabelece a autenticação cookie com uma chamada para AddIdentityCookies no construtor de autenticação:

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

Autenticação por token

Para cenários nativos e móveis em que alguns clientes não dão suporte para cookies, a API de logon fornece um parâmetro para solicitação de tokens. Um token personalizado (proprietário da plataforma ASP.NET Core Identity) é emitido e pode ser usado para autenticar solicitações subsequentes. O token deve ser passado no cabeçalho Authorization como um token de portador. Um token de atualização também é fornecido. Esse token permite que o aplicativo solicite um novo token quando o antigo expirar sem forçar o usuário a fazer logon novamente.

Os tokens não são Tokens Web JSON (JWTs) padrão. O uso de tokens personalizados é intencional, pois a API interna Identity destina-se principalmente a cenários simples. A opção de token não se destina a ser um provedor de serviço de identidade ou servidor de token completos, mas sim uma alternativa à opção cookie para clientes que não podem usar cookies.

As diretrizes a seguir iniciam o processo de implementação da autenticação baseada em token com a API de logon. É necessário um código personalizado para concluir a implementação. Para obter mais informações, confira Usar o Identity para proteger um back-end da API Web para SPAs.

Em vez de a API do servidor back-end estabelecer a autenticação cookie com uma chamada à AddIdentityCookies no construtor de autenticação, a API do servidor configura a autenticação de token de portador com o método de extensão AddBearerToken. Especifique o esquema para tokens de autenticação de portador com IdentityConstants.BearerScheme.

Em Backend/Program.cs, altere os serviços de autenticação e a configuração para o seguinte:

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

Em BlazorWasmAuth/Identity/CookieAuthenticationStateProvider.cs, remova o parâmetro useCookies da cadeia de caracteres de consulta no método LoginAsync do CookieAuthenticationStateProvider:

- login?useCookies=true
+ login

Nesse ponto, você deve fornecer um código personalizado para analisar o AccessTokenResponse no cliente e gerenciar os tokens de acesso e de atualização. Para obter mais informações, confira Usar o Identity para proteger um back-end da API Web para SPAs.

Cenários Identity adicionais

Para cenários adicionais Identity fornecidos pela API, consulte Usar Identity para proteger um back-end de API Web para SPAs:

  • Proteja os pontos de extremidade selecionados
  • Autenticação por token
  • Autenticação de dois fatores (2FA)
  • Códigos de recuperação
  • Gerenciamento de informações do usuário

Aplicativos de exemplo

Neste artigo, os aplicativos de exemplo servem como referência para aplicativos autônomos do Blazor WebAssembly que acessam a ASP.NET Core Identity por meio de uma API Web de back-end. A demonstração inclui dois aplicativos:

  • Backend: um aplicativo de API Web de back-end que mantém um repositório de identidades de usuários para a ASP.NET Core Identity.
  • BlazorWasmAuth: um aplicativo de front-end autônomo do Blazor WebAssembly com autenticação de usuário.

Acesse os aplicativos de exemplo por meio da pasta da versão mais recente na raiz do repositório com o link a seguir. Os exemplos são fornecidos para o .NET 8 ou posterior. Consulte o arquivo README na pasta BlazorWebAssemblyStandaloneWithIdentity para ver as etapas para executar os aplicativos de exemplo.

Exibir ou baixar código de exemplo (como baixar)

Pacotes e código de aplicativos de API Web de back-end

O aplicativo de API Web de back-end mantém um repositório de identidades de usuários para a ASP.NET Core Identity.

Pacotes

O aplicativo usa os seguintes pacotes NuGet:

Se o aplicativo usar um provedor de banco de dados EF Core diferente do provedor na memória, não crie uma referência de pacote em seu aplicativo para Microsoft.EntityFrameworkCore.InMemory.

No arquivo de projeto do aplicativo (.csproj), a globalização invariável é configurada.

Código de aplicativo de exemplo

As configurações do aplicativo definem URLs de back-end e front-end:

  • Aplicativo Backend (BackendUrl): https://localhost:7211
  • Aplicativo BlazorWasmAuth (FrontendUrl): https://localhost:7171

O arquivo Backend.http pode ser usado para testar a solicitação de dados meteorológicos. Observe que o aplicativo BlazorWasmAuth deve estar em execução para testar o ponto de extremidade e o ponto de extremidade é embutido no código no arquivo. Para obter mais informações, consulte Usar arquivos .http no Visual Studio 2022.

A definição e a configuração a seguir são encontradas no arquivo Program do aplicativo.

A identidade do usuário com a autenticação cookie é adicionada por meio da chamada à AddAuthentication e AddIdentityCookies. Os serviços para verificações de autorização são adicionados por uma chamada à AddAuthorizationBuilder.

Recomendado apenas para demonstrações, o aplicativo usa o provedor de banco de dados na memória EF Core para o registro do contexto do banco de dados (AddDbContext). O provedor de banco de dados na memória facilita a reinicialização do aplicativo e o teste dos fluxos de registro e logon de usuários. Cada execução começa com um novo banco de dados, mas o aplicativo inclui o código de demonstração de propagação de usuário de teste, descrito posteriormente neste artigo. Se o banco de dados for alterado para SQLite, os usuários serão salvos entre sessões, mas o banco de dados deverá ser criado por meio de migrações, conforme mostrado no tutorial de introdução EF Core. Você pode usar outros provedores relacionais, como o SQL Server, para seu código de produção.

Configure Identity para usar o banco de dados EF Core e expor os pontos de extremidade Identity por meio das chamadas para AddIdentityCore, AddEntityFrameworkStores e AddApiEndpoints.

Uma política de CORS (compartilhamento de recursos entre origens) é estabelecida para permitir solicitações dos aplicativos de front-end e back-end. As URLs de fallback são configuradas para a política CORS se as configurações do aplicativo não as fornecerem:

  • Aplicativo Backend (BackendUrl): https://localhost:5001
  • Aplicativo BlazorWasmAuth (FrontendUrl): https://localhost:5002

Serviços e pontos de extremidade para Swagger/OpenAPI estão incluídos para a documentação da API Web e o teste de desenvolvimento. Para obter mais informações sobre o NSwag, consulte Introdução ao NSwag e ao ASP.NET Core.

As declarações de função de usuário são enviadas de uma API mínima no ponto de extremidade /roles.

As rotas são mapeadas para pontos de extremidade da Identity chamando MapIdentityApi<AppUser>().

Um ponto de extremidade de logoff (/Logout) é configurado no pipeline do middleware para desconectar usuários.

Para proteger um ponto de extremidade, adicione o método de extensão RequireAuthorization à definição de rota. Para um controlador, adicione o atributo [Authorize] ao controlador ou à ação.

Para obter mais informações sobre padrões básicos de inicialização e configuração de uma instância de DbContext, consulte Configuração do tempo de vida e inicialização do DbContext na documentação do EF Core.

Pacotes e código de aplicativos Blazor WebAssembly autônomos de front-end

Um aplicativo de front-end autônomo Blazor WebAssembly demonstra a autenticação e a autorização do usuário para acessar uma página da Web privada.

Pacotes

O aplicativo usa os seguintes pacotes NuGet:

Código de aplicativo de exemplo

A pasta Models contém os modelos do aplicativo:

A interface IAccountManagement (Identity/CookieHandler.cs) fornece serviços de gerenciamento de conta.

A classe CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs) manipula o estado para autenticação baseada em cookiee fornece implementações de serviço de gerenciamento de conta descritas pela interface IAccountManagement. O método LoginAsync habilita explicitamente a autenticação cookie por meio do valor true da cadeia de caracteres de consulta useCookies. A classe também gerencia a criação de declarações de função para usuários autenticados.

A classe CookieHandler (Identity/CookieHandler.cs) garante que as credenciais do cookie sejam enviadas com cada solicitação à API Web de back-end, que manipula a Identity e mantém o armazenamento de dados da Identity.

O wwwroot/appsettings.file fornece pontos de extremidade de URL de back-end e front-end.

O componente App expõe o estado da autenticação como um parâmetro em cascata. Para obter mais informações, confira Autenticação e autorização no ASP.NET CoreBlazor.

O componente MainLayout e o componente NavMenu usam o componente AuthorizeView para exibir seletivamente o conteúdo com base no status de autenticação do usuário.

Os seguintes componentes lidam com tarefas comuns de autenticação de usuários, fazendo uso de serviços IAccountManagement:

O componente PrivatePage (Components/Pages/PrivatePage.razor) requer autenticação e mostra as declarações do usuário.

Os serviços e a configuração são fornecidos no arquivo Program (Program.cs):

  • O manipulador cookie é registrado como um serviço com escopo.
  • Os serviços de autorização são registrados.
  • O provedor de estado de autenticação personalizado é registrado como um serviço com escopo.
  • A interface de gerenciamento de conta (IAccountManagement) é registrada.
  • A URL do host base é configurada para uma instância de cliente HTTP registrada.
  • A URL de back-end de base é configurada para uma instância de cliente HTTP registrada que é usada para interações de autenticação com a API Web de back-end. O cliente HTTP usa o manipulador de cookie para garantir que as credenciais de cookie sejam enviadas com cada solicitação.

Chame AuthenticationStateProvider.NotifyAuthenticationStateChanged quando o estado de autenticação do usuário for alterado. Para obter um exemplo, confira os métodos LoginAsync e LogoutAsync da classe CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs).

Aviso

O componente AuthorizeView exibe de modo seletivo o conteúdo da interface do usuário, caso o usuário esteja autorizado. Todo o conteúdo em um aplicativo Blazor WebAssembly colocado em um componente AuthorizeView é detectável sem autenticação, portanto, o conteúdo confidencial deve ser obtido por uma API Web baseada em servidor de back-end após a autenticação ser bem-sucedida. Para saber mais, consulte os recursos a seguir:

Testar demonstração de propagação de usuário

A classe SeedData (SeedData.cs) demonstra como criar usuários de teste para desenvolvimento. O usuário de teste, chamado Leela, entra no aplicativo com o endereço de email leela@contoso.com. A senha do usuário é definida como Passw0rd!. Leela recebe as funções Administrator e Manager para autorização, o que permite que o usuário acesse a página de gerenciamento em /private-manager-page, mas não a página do editor em /private-editor-page.

Aviso

Nunca permita que o código do usuário de teste seja executado em um ambiente de produção. SeedData.InitializeAsync é chamado apenas no ambiente Development no arquivo Program:

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

Funções

Devido a um problema de design de estrutura (dotnet/aspnetcore nº 50037), as declarações de função não são enviadas de volta do ponto de extremidade manage/info para criar declarações de usuário para os usuários do aplicativo BlazorWasmAuth. As declarações de função são gerenciadas independentemente por meio de uma solicitação separada no método GetAuthenticationStateAsync da classe CookieAuthenticationStateProvider (Identity/CookieAuthenticationStateProvider.cs) depois que o usuário é autenticado no projeto Backend.

No CookieAuthenticationStateProvider, uma solicitação de funções é enviada ao ponto de extremidade /roles do projeto de API do servidor Backend. A resposta é lida em uma cadeia de caracteres chamando ReadAsStringAsync(). JsonSerializer.Deserialize desserializa a cadeia de caracteres em uma matriz personalizada RoleClaim. Por fim, as declarações são adicionadas à coleção de declarações do usuário.

No arquivo Program da API do servidor Backend, uma API mínima gerencia o ponto de extremidade /roles. As declarações RoleClaimType são selecionadas em um tipo anônimo e serializadas para retornar ao projeto BlazorWasmAuth com TypedResults.Json.

O ponto de extremidade de funções requer autorização chamando RequireAuthorization. Se você decidir não usar APIs mínimas em favor de controladores para pontos de extremidade de API de servidor seguros, defina o atributo [Authorize] em controladores ou ações.

Hospedagem entre domínios (configuração no mesmo site)

Os aplicativos de exemplo estão configurados para hospedar ambos os aplicativos no mesmo domínio. Se você hospedar o aplicativo Backend em um domínio diferente do aplicativo BlazorWasmAuth, remova marca de comentário do código que configura o cookie (ConfigureApplicationCookie) no arquivo Program do aplicativo Backend. Os valores padrão são:

Alterar os valores para:

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

Para obter mais informações sobre as configurações do mesmo local cookie, consulte os seguintes recursos:

Suporte à anti-falsificação

Somente o ponto de extremidade de logoff (/logout) no aplicativo Backend requer atenção para atenuar a ameaça de Solicitação Intersite Forjada (CSRF).

O ponto de extremidade de logoff verifica se há um corpo vazio para evitar ataques CSRF. Ao exigir um corpo, a solicitação deve ser feita a partir do JavaScript, que é a única maneira de acessar a autenticação cookie. O ponto de extremidade de logoff não pode ser acessado por um POST baseado em formulário. Isso impede que um site mal-intencionado faça o logoff do usuário.

Além disso, o ponto de extremidade é protegido por autorização (RequireAuthorization) para impedir o acesso anônimo.

O aplicativo cliente BlazorWasmAuth simplesmente precisa passar um objeto vazio {} no corpo da solicitação.

Fora do ponto de extremidade de logoff, a mitigação de antiforgery só é necessária ao enviar dados de formulário para o servidor codificados como application/x-www-form-urlencoded, multipart/form-data ou text/plain. O Blazor gerencia a atenuação de CSRF para formulários na maioria dos casos. Para mais informações, confira Autenticação e autorização no ASP.NET Core Blazor e Visão geral de formulários do ASP.NET Core Blazor.

As solicitações a outros pontos de extremidade da API do servidor (API Web) com conteúdo codificado como application/json e CORS habilitado não requerem proteção CSRF. É por isso que nenhuma proteção CSRF é necessária para o ponto de extremidade de processamento de dados (/data-processing) do aplicativo Backend. O ponto de extremidade de funções (/roles) não precisa da proteção CSRF porque é um ponto de extremidade GET que não modifica nenhum estado.

Solucionar problemas

Registrando em log

Para habilitar o log de depuração ou rastreamento para autenticação do Blazor WebAssembly, confira Registro em log do Blazor no ASP.NET Core.

Erros comuns

Verifique a configuração de cada projeto. Confirme se as URLs estão corretas:

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

Se a configuração aparecer correta:

  • Analisar logs de aplicativos.

  • Examine o tráfego de rede entre o aplicativo BlazorWasmAuth e o aplicativo Backend com as ferramentas de desenvolvedor do navegador. Muitas vezes, uma mensagem de erro exata ou uma mensagem com uma indicação do que está causando o problema é retornada ao cliente pelo aplicativo de back-end após uma solicitação. As diretrizes das ferramentas de desenvolvedor são encontradas nos seguintes artigos:

  • Google Chrome (Documentação do Google)

  • Microsoft Edge

  • Mozilla Firefox (Documentação do Mozilla)

A equipe de documentação responde aos comentários e bugs de documentação em artigos. Abra um problema usando o link Abrir um problema de documentação na parte inferior do artigo. A equipe não pode fornecer suporte ao produto. Vários fóruns de suporte público estão disponíveis para ajudar na solução de problemas de um aplicativo. Recomendamos o seguinte:

Os fóruns anteriores não são de propriedade ou controlados pela Microsoft.

Para relatórios de bugs de estrutura reproduzível não confidenciais e não confidenciais, abra um problema com a unidade do produto do ASP.NET Core. Não abra um problema com a unidade do produto até que você investigue completamente a causa de um problema e não possa resolvê-lo por conta própria e com a ajuda da comunidade em um fórum de suporte público. A unidade do produto não é capaz de solucionar problemas de aplicativos individuais que estão não estão funcionando devido a uma simples configuração incorreta ou casos de uso envolvendo serviços de terceiros. Se um relatório for confidencial por natureza ou descrever uma possível falha de segurança no produto que os invasores podem explorar, veja Como relatar problemas de segurança e bugs (repositório dotnet/aspnetcore do GitHub).

Cookies e dados do site

Cookies e dados do site podem persistir entre as atualizações do aplicativo e interferir em testes e solução de problemas. Desmarque o seguinte ao fazer alterações no código do aplicativo, alterações na conta de usuário ou alterações na configuração do aplicativo:

  • cookies de entrada do usuário
  • cookies de aplicativo
  • Dados do site armazenados e em cache

Uma abordagem para impedir que os dados persistentes de cookies e de sites interfiram no teste e na solução de problemas é:

  • Configurar um navegador
    • Use um navegador para testar se você consegue configurar a exclusão de todos os dados de cookies e sites sempre que o navegador é fechado.
    • Verifique se o navegador está fechado manualmente ou pelo IDE para qualquer alteração no aplicativo, usuário de teste ou configuração do provedor.
  • Use um comando personalizado para abrir um navegador no modo InPrivate ou Incógnito no Visual Studio:
    • Abra a caixa de diálogo Procurar com no botão Executar do Visual Studio.
    • Selecione o botão Adicionar.
    • Forneça o caminho para o navegador no campo Programa. Os seguintes caminhos executáveis são locais de instalação típicos para Windows 10. Se o navegador estiver instalado em um local diferente ou você não estiver usando Windows 10, forneça o caminho para o executável do navegador.
      • 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
    • No campo Argumentos, forneça a opção de linha de comando que o navegador usa para abrir no modo InPrivate ou Incógnito. Alguns navegadores exigem a URL do aplicativo.
      • Microsoft Edge: use -inprivate.
      • Google Chrome: use --incognito --new-window {URL}, em que o espaço reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001).
      • Mozilla Firefox: use -private -url {URL}, em que o espaço reservado {URL} é a URL a ser aberta (por exemplo, https://localhost:5001).
    • Forneça um nome no campo Nome amigável. Por exemplo, Firefox Auth Testing.
    • Selecione o botão OK.
    • Para evitar a necessidade de selecionar o perfil do navegador para cada iteração de teste com um aplicativo, defina o perfil como o padrão com o botão Definir como Padrão.
    • Verifique se o navegador está fechado pelo IDE para qualquer alteração no aplicativo, usuário de teste ou configuração do provedor.

Atualizações de aplicativos

Um aplicativo em funcionamento pode falhar imediatamente depois de atualizar o SDK do .NET Core no computador de desenvolvimento ou alterar as versões do pacote dentro do aplicativo. Em alguns casos, pacotes incoerentes podem interromper um aplicativo ao executar atualizações principais. A maioria desses problemas pode ser corrigida seguindo estas instruções:

  1. Limpe os caches do pacote NuGet do sistema local executando dotnet nuget locals all --clear de um shell de comando.
  2. Exclua as pastas bin e obj do projeto.
  3. Restaure e recompile o projeto.
  4. Exclua todos os arquivos na pasta de implantação no servidor antes de reimplantar o aplicativo.

Observação

Não há suporte para o uso de versões de pacote incompatíveis com a estrutura de destino do aplicativo. Para obter informações sobre um pacote, use a Galeria do NuGet ou a Gerenciador de Pacotes FuGet.

Inspecionar as declarações de usuário

Para solucionar problemas com declarações de usuário, o componente UserClaims a seguir pode ser usado diretamente nos aplicativos ou servir como base para personalização adicional.

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 adicionais