Adicionar autenticação ao seu aplicativo .NET Core (MVC)
- A demonstração a seguir é construída no .NET Core 8.0. O SDK é compatível com .NET 6.0 ou superior.
- 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.
- Um aplicativo web tradicional Logto criado.
Instalação
Adicione o pacote NuGet ao seu projeto:
dotnet add package Logto.AspNetCore.Authentication
Integração
Adicionar autenticação Logto
Abra Startup.cs
(ou Program.cs
) e adicione o seguinte código para registrar os serviços de autenticação do Logto:
using Logto.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddLogtoAuthentication(options =>
{
options.Endpoint = builder.Configuration["Logto:Endpoint"]!;
options.AppId = builder.Configuration["Logto:AppId"]!;
options.AppSecret = builder.Configuration["Logto:AppSecret"];
});
O método AddLogtoAuthentication
fará as seguintes coisas:
- Definir o esquema de autenticação padrão para
LogtoDefaults.CookieScheme
. - Definir o esquema de desafio padrão para
LogtoDefaults.AuthenticationScheme
. - Definir o esquema de logout padrão para
LogtoDefaults.AuthenticationScheme
. - Adicionar manipuladores de autenticação de cookie e OpenID Connect ao esquema de autenticação.
Fluxos de login e logout
Antes de prosseguirmos, há dois termos confusos no middleware de autenticação do .NET Core que precisamos esclarecer:
- CallbackPath: O URI para o qual o Logto redirecionará o usuário após o usuário ter feito login (o "URI de redirecionamento" no Logto)
- RedirectUri: O URI para o qual será redirecionado após as ações necessárias terem sido realizadas no middleware de autenticação do Logto.
O processo de login pode ser ilustrado da seguinte forma:
Da mesma forma, o .NET Core também possui SignedOutCallbackPath e RedirectUri para o fluxo de logout.
Para maior clareza, nos referiremos a eles da seguinte forma:
Termo que usamos | Termo do .NET Core |
---|---|
URI de redirecionamento do Logto | CallbackPath |
URI de redirecionamento pós-logout do Logto | SignedOutCallbackPath |
URI de redirecionamento do aplicativo | RedirectUri |
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.
Configurar URIs de redirecionamento
Nos trechos de código a seguir, assumimos que seu aplicativo está sendo executado em http://localhost:3000/
.
Primeiro, vamos configurar o URI de redirecionamento do Logto. Adicione o seguinte URI à lista de "Redirect URIs" na página de detalhes do aplicativo Logto:
http://http://localhost:3000//Callback
Para configurar o URI de redirecionamento pós logout do Logto, adicione o seguinte URI à lista de "Post sign-out redirect URIs" na página de detalhes do aplicativo Logto:
http://http://localhost:3000//SignedOutCallback
Alterar os caminhos padrão
O URI de redirecionamento do Logto tem um caminho padrão de /Callback
, e o URI de redirecionamento pós logout do Logto tem um caminho padrão de /SignedOutCallback
.
Você pode deixá-los como estão se não houver nenhum requisito especial. Se você quiser alterá-los, pode definir a propriedade CallbackPath
e SignedOutCallbackPath
para LogtoOptions
:
builder.Services.AddLogtoAuthentication(options =>
{
// Outras configurações...
options.CallbackPath = "/Foo";
options.SignedOutCallbackPath = "/Bar";
});
Lembre-se de atualizar o valor na página de detalhes do aplicativo Logto de acordo.
Implementar botões de login e logout
Primeiro, adicione métodos de ações ao seu Controller
, por exemplo:
public class HomeController : Controller
{
public IActionResult SignIn()
{
// Isso redirecionará o usuário para a página de login do Logto.
return Challenge(new AuthenticationProperties { RedirectUri = "/" });
}
// Use a palavra-chave `new` para evitar conflito com o método `ControllerBase.SignOut`
new public IActionResult SignOut()
{
// Isso limpará o cookie de autenticação e redirecionará o usuário para a página de logout do Logto
// para limpar a sessão do Logto também.
return SignOut(new AuthenticationProperties { RedirectUri = "/" });
}
}
Em seguida, adicione os links à sua View:
<p>Está autenticado: @User.Identity?.IsAuthenticated</p>
@if (User.Identity?.IsAuthenticated == true) {
<a asp-controller="Home" asp-action="SignOut">Sair</a>
} else {
<a asp-controller="Home" asp-action="SignIn">Entrar</a>
}
Ele mostrará o link "Entrar" se o usuário não estiver autenticado e mostrará o link "Sair" se o usuário estiver autenticado.
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
Para saber se o usuário está autenticado, você pode verificar a propriedade User.Identity?.IsAuthenticated
.
Para obter as reivindicações do perfil do usuário, você pode usar a propriedade User.Claims
:
var claims = User.Claims;
// Obter o ID do usuário
var userId = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Subject)?.Value;
Veja LogtoParameters.Claims
para a lista de nomes de reivindicações e seus significados.
Solicitar reivindicações adicionais
Você pode perceber que algumas informações do usuário estão faltando no objeto retornado de User.Claims
. 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 configurar a propriedade Scopes
no objeto options
:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Scopes = new string[] {
LogtoParameters.Scopes.Email,
LogtoParameters.Scopes.Phone
}
});
Então você pode acessar as reivindicações adicionais via User.Claims
:
var claims = User.Claims;
// Obter o email do usuário
var email = claims.FirstOrDefault(c => c.Type == LogtoParameters.Claims.Email)?.Value;
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 GetClaimsFromUserInfoEndpoint
como true
no objeto options
:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.GetClaimsFromUserInfoEndpoint = 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.
Configurar recurso de API em seu aplicativo
Depois de configurar os recursos de API, você pode adicioná-los ao configurar o Logto em seu aplicativo:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Resource = "https://<seu-indicador-de-recurso-de-api>";
});
Cada recurso de API tem suas próprias permissões (escopos).
Por exemplo, o recurso https://shopping.your-app.com/api
tem as permissões shopping:read
e shopping:write
, e o recurso https://store.your-app.com/api
tem as permissões store:read
e store:write
.
Para solicitar essas permissões, você pode adicioná-las ao configurar o Logto em seu aplicativo:
builder.Services.AddLogtoAuthentication(options =>
{
// ...
options.Resource = "https://shopping.your-app.com/api";
options.Scopes = new string[] {
"openid",
"profile",
"offline_access",
"read",
"write"
};
});
Você pode notar que os escopos são definidos separadamente dos recursos de API. Isso ocorre porque Resource Indicators for OAuth 2.0 especifica que os escopos finais para a solicitação serão o produto cartesiano de todos os escopos em todos os serviços de destino.
Não há problema em solicitar escopos que não estão definidos nos recursos de API. Por exemplo, você pode solicitar o escopo email
mesmo que os recursos de API não tenham o escopo email
disponível. Escopos indisponíveis serão ignorados com segurança.
Após o login bem-sucedido, o Logto emitirá os escopos apropriados para os recursos de API de acordo com os papéis do usuário.
Buscar tokens
Às vezes, você pode precisar buscar o token de acesso ou o token de ID para chamadas de API. Você pode usar o método GetTokenAsync
para buscar os tokens:
var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessToken);
var idToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.IdToken);
Não há necessidade de se preocupar com a expiração do token, o middleware de autenticação irá automaticamente atualizar os tokens quando necessário.
Embora o middleware de autenticação atualize automaticamente os tokens, as reivindicações (Claims) no objeto do usuário não serão atualizadas devido à limitação do manipulador de autenticação OpenID Connect subjacente. Isso pode ser resolvido assim que escrevermos nosso próprio manipulador de autenticação.
Observe que o token de acesso acima é um token opaco (Opaque token) para o endpoint userinfo no OpenID Connect, que não é um token JWT. Se você especificou o recurso de API, precisa usar LogtoParameters.Tokens.AccessTokenForResource
para buscar o token de acesso para o recurso de API:
var accessToken = await HttpContext.GetTokenAsync(LogtoParameters.Tokens.AccessTokenForResource);
Este token será um token JWT com o recurso de API como o público (Audience).