Blazor WebAssembly - Autenticação com Identity Server


  Hoje veremos como usar a autenticação em uma aplicação Blazor WebAssembly configurando a opção Individual Accounts

Ao criar um projeto Blazor WebAssembly com a opção "Individual Accounts", a autenticação é implementada usando o padrão de autenticação baseado em tokens chamado OpenID Connect. Essa opção é especialmente útil para aplicativos que exigem autenticação de usuários individuais.



Quando você seleciona a opção "Individual Accounts" durante a criação do projeto, o Visual Studio (ou a CLI do .NET) configura automaticamente o projeto com suporte à autenticação, adiciona as dependências necessárias e configura o pipeline de autenticação e autorização.

A autenticação Individual Accounts usa o IdentityServer como provedor de identidade e autenticação. O IdentityServer é uma framework que fornece serviços de autenticação, autorização e gerenciamento de tokens e permite que os usuários se autentiquem usando vários provedores, como contas locais (usuário/senha), Google, Facebook, Microsoft, Twitter, entre outros.

O projeto criado vai incluir várias páginas e componentes que lidam com a autenticação, como páginas de registro, login, gerenciamento de perfil e recuperação de senha e também vai incluir serviços e classes que interagem com o IdentityServer para gerenciar tokens de acesso e autenticação.

Criando o projeto

Vamos abrir o Visual Studio 2022 clicar em Create a new Project e selecionar o template Blazor WebAssembly app :

Informe o nome do projeto BlazorWasmAutenticacao e a seguir defina as seguintes configurações :

Ao clicar no botão Create teremos o projeto com a seguinte estrutura:

Temos aqui o projeto Client , o projeto Server e o projeto Shared :

  1. Projeto Server : É um projeto ASP.NET Core que atua como o servidor de back-end para o aplicativo Blazor WebAssembly. Ele fornece serviços de autenticação, autorização, manipulação de dados e lógica de negócios. Além disso, é responsável por hospedar os recursos estáticos do cliente (como arquivos HTML, CSS, JavaScript) e fornecer APIs e endpoints para o cliente interagir.
    O projeto de servidor normalmente contém as seguintes partes principais:
    - Controladores (Controllers): Responsáveis por lidar com as solicitações HTTP e fornecer respostas aos clientes.
    - Serviços (Services): Responsáveis por implementar a lógica de negócios e manipulação de dados.
    - Configuração de autenticação e autorização: Define como a autenticação e a autorização são tratadas no aplicativo.
    - APIs e endpoints: Fornecem pontos de acesso para o cliente interagir com o servidor.
  1. Projeto Client : É um projeto Blazor WebAssembly que contém o código do aplicativo que será executado no navegador do cliente. Ele é responsável por renderizar a interface do usuário, lidar com a interatividade e a lógica do aplicativo no lado do cliente.
    O projeto de cliente normalmente contém as seguintes partes principais:
    - Componentes Blazor: São as unidades fundamentais da interface do usuário no Blazor, compostas de HTML, C# e lógica de renderização.
    - Serviços de cliente (Client Services): São responsáveis por lidar com a comunicação entre o cliente e o servidor, como fazer solicitações HTTP para obter ou enviar dados.
    - Configurações de roteamento: Define as rotas e o fluxo de navegação no aplicativo Blazor.
    - Páginas de inicialização: São as páginas que são carregadas inicialmente quando o aplicativo é executado no navegador.
     
  2. Projeto Shared : É um projeto de biblioteca de classes que contém código compartilhado entre o projeto de servidor e o projeto de cliente. Ele permite que você compartilhe tipos, modelos de dados, serviços e outros recursos entre o cliente e o servidor, evitando duplicação de código.
    O projeto compartilhado pode conter:
    - Modelos de dados: Classes que representam os objetos e estruturas de dados usados no aplicativo.
    - Interfaces e serviços compartilhados: Definições de interfaces e implementações de serviços que são usados tanto no cliente quanto no servidor.
    - Bibliotecas de componentes reutilizáveis: Componentes Blazor que podem ser compartilhados entre o cliente e o servidor.

Será apresentado um arquivo readme.txt com o seguinte texto:

This code includes a dependency on Duende IdentityServer.
This is an open source product with a reciprocal license agreement. If you plan to use Duende IdentityServer in production this may require a license fee.
To see how to use Azure Active Directory for your identity please see https://aka.ms/aspnetidentityserver
To see if you require a commercial license for Duende IdentityServer please see https://aka.ms/identityserverlicense

Este texto basicamente te informa que o código gerado inclui dependências para o Duende IdentityServer e que para usar este recurso em produção você vai ter que pagar.

Cabe destacar que no projeto Server esta definida toda a configuração da autenticação usando o Duende IdentityServer, e que na classe Program esta definido o código para o serviço do IdentityServer:

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
     ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

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

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
    app.UseWebAssemblyDebugging();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseRouting();

app.UseIdentityServer();
app.UseAuthorization();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();

Vamos entender as principais partes do código :

  1. var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");:
    Obtém a string de conexão do banco de dados do arquivo de configuração do aplicativo. A string de conexão é recuperada usando a chave "DefaultConnection". Se a string de conexão não for encontrada, uma exceção é lançada.
     
  2. builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));:
    Adiciona o contexto do banco de dados do Entity Framework Core (ApplicationDbContext) aos serviços do contêiner de injeção de dependência. O contexto do banco de dados é configurado para usar o provedor do SQL Server (UseSqlServer) com a string de conexão especificada.
     
  3. builder.Services.AddDatabaseDeveloperPageExceptionFilter();:
    Adiciona um filtro de exceção para mostrar informações úteis em páginas de erros durante o desenvolvimento. Essas páginas de erro são voltadas para desenvolvedores e fornecem informações detalhadas sobre exceções relacionadas ao banco de dados.
     
  4. builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>();
    Configuram a identidade padrão para o aplicativo. O método AddDefaultIdentity adiciona o suporte à autenticação de identidade para os usuários. Aqui, a classe ApplicationUser é usada como a classe de usuário personalizada. O parâmetro options é usado para configurar as opções de identidade, como exigir uma conta confirmada (RequireConfirmedAccount). O método AddEntityFrameworkStores é usado para configurar o armazenamento da identidade usando o ApplicationDbContext.
     
  5. builder.Services.AddIdentityServer() .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    Configuram o IdentityServer, que é responsável pela autenticação e autorização do aplicativo. O método AddIdentityServer adiciona o IdentityServer aos serviços do contêiner de injeção de dependência. O método AddApiAuthorization configura a autorização para APIs usando a identidade do usuário personalizada ApplicationUser e o contexto do banco de dados ApplicationDbContext.
     
  6. builder.Services.AddAuthentication() .AddIdentityServerJwt();
    Configura o esquema de autenticação para usar o IdentityServer como provedor de autenticação JWT (JSON Web Token). O método AddAuthentication adiciona o esquema de autenticação aos serviços do contêiner. O método AddIdentityServerJwt configura o esquema de autenticação para usar o IdentityServer como provedor JWT.

O projeto é criado com uma migração pendente que será aplicada na primeira tentativa de acessar os dados da aplicação.

Antes de fazer isso você pode desejar alterar a string de conexão definida (nome do server, do banco, etc.) no arquivo appsettings.json:

"ConnectionStrings": {
  
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorWasmAutenticacao.Server-d59b4f22-6f5c-41fe-9af2-7e287977b737;Trusted_Connection=True;MultipleActiveResultSets=true"
},
...

Cabe destacar que se você estiver usando o EF Core 7.0 vai precisar incluir a propriedade TrustServerCertificate=True; na string de conexão.

Ao executar o projeto, você verá uma interface de usuário que permite que os usuários se registrem, façam login, gerenciem seus perfis e realizem ações específicas apenas para usuários autenticados. A autenticação é baseada em tokens JWT (JSON Web Tokens), onde um token de acesso é emitido para um usuário autenticado e usado para autenticação subsequente em cada solicitação para o servidor.

Ao clicar em Register será apresentada a página abaixo onde podemos informar o email e a senha e clicar em Register:

Com isso veremos a página indicando que a operação com o banco falhou e que é preciso aplicar a migração pendente clicando no botão Apply MIgrations :

Após clicar no botão será exibida a mensagem para atualizar a página pressionando F5 :

A seguir temos que confirmar a criação da conta clicando no link :  Click here to confirm your account

Neste momento se você inspecionar o banco de dados usando o SQL Server Management Studio verá que foi criado o usuário admin@localhost na tabela AspNetUsers:

Observe que a coluna EmailConfirmed esta marcada com o valor True indicando que o email da conta foi confirmado.

A seguir podemos clicar no link para fazer o login:

E fazer o login informando as credenciais e clicando em Log in :

Com isso teremos acesso a aplicação e poderemos acessar os dados exibindo-os na página onde veremos o nome do usuário atual logado:

No navegador clicando em Inspecionar e selecionando a opção Rede ao clicar opção Fetch Data teremos os dados exibidos e poderemos verificar o Header de autorização exibindo o token enviado no request para o usuário autenticado:

Selecionando a opção Armazenamento no navegador e verificando o item Armazenamento de sessão veremos as informações do user OIDC:

O termo "usuário OIDC" se refere a um usuário autenticado e registrado no servidor de identidade (IdentityServer). OIDC é um protocolo de autenticação baseado em tokens que permite que aplicativos obtenham informações sobre a identidade de um usuário autenticado.

Quando um usuário faz login em um aplicativo que utiliza o IdentityServer como provedor de identidade, o IdentityServer autentica o usuário e emite um token de identificação, conhecido como ID Token. Esse ID Token contém informações sobre o usuário autenticado, como seu identificador único, nome, endereço de e-mail, entre outros atributos.

O usuário OIDC é essencialmente o objeto que representa o usuário autenticado no contexto do IdentityServer. O IdentityServer gera um identificador exclusivo para cada usuário registrado e autenticado, chamado OpenID Connect User Identifier (sub). Esse identificador é usado para referenciar e identificar o usuário em várias interações com o IdentityServer.

O usuário OIDC é fundamental para a implementação de autenticação e autorização em aplicativos que utilizam o IdentityServer. Ele permite que os aplicativos autentiquem os usuários por meio do IdentityServer e obtenham informações sobre a identidade do usuário para personalizar a experiência do usuário, autorizar o acesso a recursos protegidos e fornecer serviços personalizados com base nas características do usuário.

Dessa forma usando esta opção para criar o seu projeto Blazor você terá todas essas funcionalidades definidas e prontas para serem usadas.

E estamos conversados ...

"Jesus dizia a todos: "Se alguém quiser acompanhar-me, negue-se a si mesmo, tome diariamente a sua cruz e siga-me."
Lucas 9:23

Referências:


José Carlos Macoratti