Adicionar autenticação ao seu aplicativo .NET Core (Blazor WASM)
- A demonstração a seguir é construída em .NET Core 8.0 e Blorc.OpenIdConnect.
- Os projetos de exemplo do .NET Core estão disponíveis no repositório GitHub.
Pré-requisitos
- Uma conta Logto Cloud ou um Logto auto-hospedado.
- Uma aplicação de página única Logto criada.
Instalação
Adicione o pacote NuGet ao seu projeto:
dotnet add package Blorc.OpenIdConnect
Integração
Adicionar referências de script
Inclua Blorc.Core/injector.js
no arquivo index.html
:
<head>
<!-- ... -->
<script src="_content/Blorc.Core/injector.js"></script>
<!-- ... -->
</head>
Registrar serviços
Adicione o seguinte código ao arquivo Program.cs
:
using Blorc.OpenIdConnect;
using Blorc.Services;
builder.Services.AddBlorcCore();
builder.Services.AddAuthorizationCore();
builder.Services.AddBlorcOpenIdConnect(
options =>
{
builder.Configuration.Bind("IdentityServer", options);
});
var webAssemblyHost = builder.Build();
await webAssemblyHost
.ConfigureDocumentAsync(async documentService =>
{
await documentService.InjectBlorcCoreJsAsync();
await documentService.InjectOpenIdConnectAsync();
});
await webAssemblyHost.RunAsync();
Não há necessidade de usar o pacote Microsoft.AspNetCore.Components.WebAssembly.Authentication
. O pacote Blorc.OpenIdConnect
cuidará do processo de autenticação.
Configurar URIs de redirecionamento
Antes de mergulharmos nos detalhes, aqui está uma visão geral rápida da experiência do usuário final. O processo de login pode ser simplificado da seguinte forma:
- Seu aplicativo invoca o método de login.
- O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
- O usuário faz login e é redirecionado de volta para o seu aplicativo (configurado como o URI de redirecionamento).
Sobre o login baseado em redirecionamento
- Este processo de autenticação segue o protocolo OpenID Connect (OIDC), e o Logto aplica medidas de segurança rigorosas para proteger o login do usuário.
- Se você tiver vários aplicativos, pode usar o mesmo provedor de identidade (Logto). Uma vez que o usuário faz login em um aplicativo, o Logto completará automaticamente o processo de login quando o usuário acessar outro aplicativo.
Para saber mais sobre a lógica e os benefícios do login baseado em redirecionamento, veja Experiência de login do Logto explicada.
Nos trechos de código a seguir, assumimos que seu aplicativo está sendo executado em http://localhost:3000/
.
Configurar URIs de redirecionamento
Vá para a página de detalhes do aplicativo no Logto Console. Adicione um URI de redirecionamento http://localhost:3000/callback
.
Assim como no login, os usuários devem ser redirecionados para o Logto para sair da sessão compartilhada. Uma vez concluído, seria ótimo redirecionar o usuário de volta para o seu site. Por exemplo, adicione http://localhost:3000/
como a seção de URI de redirecionamento pós logout.
Em seguida, clique em "Salvar" para salvar as alterações.
Configurar aplicativo
Adicione o seguinte código ao arquivo appsettings.json
:
{
// ...
IdentityServer: {
Authority: 'https://<your-logto-endpoint>/oidc',
ClientId: '<your-logto-app-id>',
PostLogoutRedirectUri: 'http://localhost:3000/',
RedirectUri: 'http://localhost:3000/callback',
ResponseType: 'code',
Scope: 'openid profile', // Adicione mais escopos se necessário
},
}
Lembre-se de adicionar o RedirectUri
e o PostLogoutRedirectUri
à lista de URIs de redirecionamento permitidos nas configurações do aplicativo Logto. Ambos são a URL do seu aplicativo WASM.
Adicionar componente AuthorizeView
Nas páginas Razor que requerem autenticação, adicione o componente AuthorizeView
. Vamos supor que seja a página Home.razor
:
@using Microsoft.AspNetCore.Components.Authorization
@page "/"
<AuthorizeView>
<Authorized>
@* Visualização autenticada *@
<button @onclick="OnLogoutButtonClickAsync">
Sair
</button>
</Authorized>
<NotAuthorized>
@* Visualização não autenticada *@
<button @onclick="OnLoginButtonClickAsync">
Entrar
</button>
</NotAuthorized>
</AuthorizeView>
Configurar autenticação
No arquivo Home.razor.cs
(crie-o se não existir), adicione o seguinte código:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Blorc.OpenIdConnect;
using Microsoft.AspNetCore.Components.Authorization;
[Authorize]
public partial class Home : ComponentBase
{
[Inject]
public required IUserManager UserManager { get; set; }
public User<Profile>? User { get; set; }
[CascadingParameter]
protected Task<AuthenticationState>? AuthenticationStateTask { get; set; }
protected override async Task OnInitializedAsync()
{
User = await UserManager.GetUserAsync<User<Profile>>(AuthenticationStateTask!);
}
private async Task OnLoginButtonClickAsync(MouseEventArgs obj)
{
await UserManager.SignInRedirectAsync();
}
private async Task OnLogoutButtonClickAsync(MouseEventArgs obj)
{
await UserManager.SignOutRedirectAsync();
}
}
Uma vez que o usuário é autenticado, a propriedade User
será preenchida com as informações do usuário.
Ponto de verificação: Teste seu aplicativo
Agora, você pode testar seu aplicativo:
- Execute seu aplicativo, você verá o botão de login.
- Clique no botão de login, o SDK iniciará o processo de login e redirecionará você para a página de login do Logto.
- Após fazer login, você será redirecionado de volta para o seu aplicativo e verá o botão de logout.
- Clique no botão de logout para limpar o armazenamento local e sair.
Obter informações do usuário
Exibir informações do usuário
Aqui estão alguns exemplos de como exibir informações do usuário na página Home.razor
:
<AuthorizeView>
<Authorized>
@* Visualização de login *@
@* ... *@
<p>Você está logado como @(@User?.Profile?.Name ?? "(nome desconhecido)").</p>
</Authorized>
@* ... *@
</AuthorizeView>
Para mais propriedades e reivindicações, verifique as classes User
e Profile
no pacote Blorc.OpenIdConnect
.
Solicitar reivindicações adicionais
Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de User
. Isso ocorre porque OAuth 2.0 e OpenID Connect (OIDC) são projetados para seguir o princípio do menor privilégio (PoLP), e o Logto é construído com base nesses padrões.
Por padrão, reivindicações limitadas são retornadas. Se você precisar de mais informações, pode solicitar escopos adicionais para acessar mais reivindicações.
Uma "reivindicação (Claim)" é uma afirmação feita sobre um sujeito; um "escopo (Scope)" é um grupo de reivindicações. No caso atual, uma reivindicação é uma informação sobre o usuário.
Aqui está um exemplo não normativo da relação escopo - reivindicação:
A reivindicação "sub" significa "sujeito (Subject)", que é o identificador único do usuário (ou seja, ID do usuário).
O Logto SDK sempre solicitará três escopos: openid
, profile
e offline_access
.
Para solicitar escopos adicionais, você pode adicionar escopos válidos à propriedade IdentityServer.Scope
no arquivo appsettings.json
.
{
// ...
"IdentityServer": {
// ...
"Scope": "openid profile email phone"
}
}
Reivindicações que precisam de solicitação de rede
Para evitar sobrecarregar o objeto do usuário, algumas reivindicações requerem solicitações de rede para serem buscadas. Por exemplo, a reivindicação custom_data não está incluída no objeto do usuário, mesmo que seja solicitada nos escopos. Para buscar essas reivindicações, você pode definir a propriedade IdentityServer.LoadUserInfo
como true
no arquivo appsettings.json
.
Por exemplo, para buscar o endereço de email do usuário e dados personalizados, você pode usar a seguinte configuração:
{
// ...
"IdentityServer": {
// ...
"Scope": "openid profile email custom_data",
"LoadUserInfo": true
}
}
Escopos e reivindicações
Logto usa as convenções de escopos e reivindicações do OIDC para definir os escopos e reivindicações para recuperar informações do usuário do Token de ID e do endpoint userinfo do OIDC. Tanto "escopo" quanto "reivindicação" são termos das especificações OAuth 2.0 e OpenID Connect (OIDC).
Aqui está a lista de Escopos (Scopes) suportados e as Reivindicações (Claims) correspondentes:
openid
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
sub | string | O identificador único do usuário | Não |
profile
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
name | string | O nome completo do usuário | Não |
username | string | O nome de usuário do usuário | Não |
picture | string | URL da foto de perfil do Usuário Final. Este URL DEVE referir-se a um arquivo de imagem (por exemplo, um arquivo de imagem PNG, JPEG ou GIF), em vez de uma página da Web contendo uma imagem. Note que este URL DEVE referenciar especificamente uma foto de perfil do Usuário Final adequada para exibição ao descrever o Usuário Final, em vez de uma foto arbitrária tirada pelo Usuário Final. | Não |
created_at | number | Hora em que o Usuário Final foi criado. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z). | Não |
updated_at | number | Hora em que as informações do Usuário Final foram atualizadas pela última vez. O tempo é representado como o número de milissegundos desde a época Unix (1970-01-01T00:00:00Z). | Não |
Outras reivindicações padrão incluem family_name
, given_name
, middle_name
, nickname
, preferred_username
, profile
, website
, gender
, birthdate
, zoneinfo
e locale
também serão incluídas no escopo profile
sem a necessidade de solicitar o endpoint userinfo. Uma diferença em relação às reivindicações acima é que essas reivindicações só serão retornadas quando seus valores não estiverem vazios, enquanto as reivindicações acima retornarão null
se os valores estiverem vazios.
Ao contrário das reivindicações padrão, as reivindicações created_at
e updated_at
estão usando milissegundos em vez de segundos.
email
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
string | O endereço de email do usuário | Não | |
email_verified | boolean | Se o endereço de email foi verificado | Não |
phone
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
phone_number | string | O número de telefone do usuário | Não |
phone_number_verified | boolean | Se o número de telefone foi verificado | Não |
address
Por favor, consulte o OpenID Connect Core 1.0 para os detalhes da reivindicação de endereço.
custom_data
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
custom_data | object | Os dados personalizados do usuário | Sim |
identities
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
identities | object | As identidades vinculadas do usuário | Sim |
sso_identities | array | As identidades SSO vinculadas do usuário | Sim |
urn:logto:scope:organizations
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organizations | string[] | Os IDs das organizações às quais o usuário pertence | Não |
organization_data | object[] | Os dados das organizações às quais o usuário pertence | Sim |
urn:logto:scope:organization_roles
Nome da Reivindicação | Tipo | Descrição | Precisa de userinfo? |
---|---|---|---|
organization_roles | string[] | Os papéis da organização aos quais o usuário pertence com o formato <organization_id>:<role_name> | Não |
Considerando o desempenho e o tamanho dos dados, se "Precisa de userinfo?" for "Sim", isso significa que a reivindicação não aparecerá no Token de ID, mas será retornada na resposta do endpoint userinfo.
Recursos de API
Recomendamos ler 🔐 Controle de Acesso Baseado em Papel (RBAC) primeiro para entender os conceitos básicos do RBAC do Logto e como configurar corretamente os recursos de API.
Por padrão, quando você acessa User?.AccessToken
, você obterá um token opaco (Opaque token) que tem um comprimento curto e não é um JWT (JSON Web Token). Este token é usado para acessar o endpoint userinfo.
Adicionar recurso de API à configuração
Para obter um token JWT que pode ser usado para acessar um recurso de API (API resource) definido no Logto, adicione os seguintes parâmetros ao arquivo appsettings.json
(tomando https://my-api-resource
como exemplo):
{
// ...
"IdentityServer": {
// ...
"Scope": "openid profile email <your-api-scopes>", // Adicione seus escopos de API aqui
"Resource": "https://my-api-resource",
"ExtraTokenParams": {
"resource": "https://my-api-resource" // Certifique-se de que a chave "resource" está em minúsculas
}
}
}
A propriedade Resource
é usada para adicionar o recurso de API à solicitação de autenticação. A propriedade ExtraTokenParams
é usada para adicionar o recurso de API à solicitação de token. Como o Logto está em conformidade com o RFC 8707 para recursos de API, ambas as propriedades são necessárias.
Como o WebAssembly é uma aplicação do lado do cliente, a solicitação de token será enviada ao lado do servidor apenas uma vez. Devido a essa natureza, LoadUserInfo
entra em conflito com a obtenção de token de acesso para recursos de API.
Usar token de acesso
Uma vez que o usuário está autenticado, você pode acessar o recurso de API usando a propriedade User?.AccessToken
. Por exemplo, você pode usar o HttpClient
para acessar o recurso de API:
using Blorc.OpenIdConnect;
builder.Services
.AddHttpClient("MyApiResource", client =>
{
client.BaseAddress = new Uri("https://my-api-resource");
})
.AddAccessToken(); // Adiciona o token de acesso ao cabeçalho da solicitação