Persista declarações e tokens adicionais de provedores externos no ASP.NET Core
Um aplicativo ASP.NET Core pode estabelecer declarações e tokens adicionais de provedores de autenticação externos, como Facebook, Google, Microsoft e Twitter. Cada provedor revela informações diferentes sobre os usuários em sua plataforma, mas o padrão para receber e transformar dados do usuário em declarações adicionais é o mesmo.
Pré-requisitos
Decida quais provedores de autenticação externos oferecer suporte no aplicativo. Para cada provedor, registre o aplicativo e obtenha um ID do cliente e um segredo do cliente. Para obter mais informações, consulte autenticação do Facebook e Google no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação do Google.
Definir o ID do cliente e o segredo do cliente
O provedor de autenticação OAuth estabelece uma relação de confiança com um aplicativo usando uma ID de cliente e segredo de cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externo quando o aplicativo é registrado no provedor. Cada provedor externo que a aplicação utiliza deve ser configurado independentemente com o ID do cliente e o segredo do cliente do provedor. Para obter mais informações, consulte os tópicos do provedor de autenticação externa que se aplicam:
- Autenticação do Facebook
- Autenticação do Google
- Autenticação Microsoft
- Autenticação do Twitter
- Outros provedores de autenticação
- OpenIdConnect
As declarações opcionais enviadas no ID ou token de acesso do provedor de autenticação geralmente são configuradas no portal online do provedor. Por exemplo, o Microsoft Entra ID permite atribuir declarações opcionais ao token de ID do aplicativo na folha de configuração do
O aplicativo de exemplo configura o provedor de autenticação do Google com um ID do cliente e um segredo do cliente fornecidos pelo Google:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebGoogOauth.Data;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
googleOptions.SaveTokens = true;
googleOptions.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options =>
options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
// Remaining code removed for brevity.
Estabelecer o escopo de autenticação
Especifique a lista de permissões a serem recuperadas do provedor especificando o Scope. Os escopos de autenticação para provedores externos comuns aparecem na tabela a seguir.
Fornecedor | Âmbito de aplicação |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , email , openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
No aplicativo de exemplo, os escopos profile
, email
e openid
do Google são adicionados automaticamente pela estrutura quando AddGoogle é chamado no AuthenticationBuilder. Se o aplicativo exigir escopos adicionais, adicione-os às opções. No exemplo a seguir, o escopo do Google https://www.googleapis.com/auth/user.birthday.read
é adicionado para recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapeie chaves de dados do usuário e crie declarações
Nas opções do fornecedor, especifique um MapJsonKey ou MapJsonSubKey para cada chave ou subchave nos dados JSON do utilizador do fornecedor externo, para que a identidade da aplicação possa ser lida durante a autenticação. Para obter mais informações sobre tipos de declaração, consulte ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) a partir das chaves locale
e picture
nos dados do usuário do Google:
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
googleOptions.SaveTokens = true;
googleOptions.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
No Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, um IdentityUser (ApplicationUser
) é conectado ao aplicativo com SignInAsync. Durante o processo de login, o UserManager<TUser> pode armazenar um ApplicationUser
declarações para dados do usuário disponíveis no Principal.
No aplicativo de exemplo, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) estabelece as configurações de localidade (urn:google:locale
) e as declarações de foto (urn:google:picture
) para o utilizador que iniciou sessão ApplicationUser
, incluindo uma declaração para GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
// using Microsoft.AspNetCore.Authentication;
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = false;
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, props, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
Por padrão, as declarações de um utilizador são armazenadas na autenticação cookie. Se o cookie de autenticação for muito grande, pode fazer com que o aplicativo falhe porque:
- O navegador deteta que o cabeçalho cookie é muito longo.
- O tamanho total da solicitação é muito grande.
Se for necessária uma grande quantidade de dados do utilizador para processar os pedidos do utilizador:
- Limite o número e o tamanho das declarações de usuário para processamento de solicitações apenas ao que o aplicativo exige.
- Use um ITicketStore personalizado para o SessionStore do Middleware de Autenticação Cookie para armazenar identidade entre solicitações. Preserve grandes quantidades de informações de identidade no servidor enquanto envia apenas uma pequena chave de identificador de sessão para o cliente.
Salve o token de acesso
SaveTokens define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida.
SaveTokens
está definido como false
por padrão para reduzir o tamanho do cookiede autenticação final.
O aplicativo de exemplo define o valor de SaveTokens
como true
em GoogleOptions:
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
googleOptions.SaveTokens = true;
googleOptions.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Quando OnPostConfirmationAsync
for executado, o token de acesso (ExternalLoginInfo.AuthenticationTokens) do provedor externo deve ser armazenado no AuthenticationProperties
do ApplicationUser
.
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(registro de novo usuário) e OnGetCallbackAsync
(usuário registrado anteriormente) em Account/ExternalLogin.cshtml.cs
:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
// using Microsoft.AspNetCore.Authentication;
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = false;
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, props, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
Observação
Para obter informações sobre como passar tokens para os componentes Razor de uma aplicação do lado do servidor Blazor, consulte ASP.NET Core do lado do servidor e os cenários de segurança adicionais Blazor Web App.
Como adicionar tokens personalizados adicionais
Para demonstrar como adicionar um token personalizado, que é armazenado como parte do SaveTokens
, a aplicação de exemplo adiciona um AuthenticationToken com o DateTime atual para uma AuthenticationToken.Name de TicketCreated
:
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
googleOptions.SaveTokens = true;
googleOptions.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Criar e adicionar declarações
A estrutura fornece ações comuns e métodos de extensão para criar e adicionar declarações à coleção. Para obter mais informações, consulte os ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método Run abstrato.
Para obter mais informações, consulte Microsoft.AspNetCore.Authentication.OAuth.Claims.
Adicionar e atualizar declarações de usuário
As declarações são copiadas de provedores externos para o banco de dados de usuários no primeiro registro, não no login. Se declarações adicionais forem habilitadas em uma aplicação depois que um utilizador se registar para usar a aplicação, chame SignInManager.RefreshSignInAsync num utilizador para forçar a geração de um novo cookiede autenticação.
No ambiente de desenvolvimento trabalhando com contas de usuário de teste, exclua e recrie a conta de usuário. Para sistemas de produção, novas declarações adicionadas ao aplicativo podem ser preenchidas de volta em contas de usuário. Depois de andaime a página ExternalLogin
no aplicativo em Areas/Pages/Identity/Account/Manage
, adicione o seguinte código ao ExternalLoginModel
no arquivo ExternalLogin.cshtml.cs
.
Adicione um dicionário de declarações adicionadas. Use as chaves do dicionário para armazenar os tipos de reivindicação e use os valores para definir um valor padrão. Adicione a seguinte linha ao topo da classe. O exemplo a seguir pressupõe que uma declaração seja adicionada para a imagem do Google do utilizador, utilizando uma fotografia genérica de rosto como valor padrão:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Substitua o código padrão do método OnGetCallbackAsync
pelo código a seguir. O código percorre o dicionário de declarações. As declarações são adicionadas (preenchidas) ou atualizadas para cada usuário. Quando as declarações são adicionadas ou atualizadas, o login do usuário é atualizado usando o SignInManager<TUser>, preservando as propriedades de autenticação existentes (AuthenticationProperties
).
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
if (_claimsToSync.Count > 0)
{
var user = await _userManager.FindByLoginAsync(info.LoginProvider,
info.ProviderKey);
var userClaims = await _userManager.GetClaimsAsync(user);
bool refreshSignIn = false;
foreach (var addedClaim in _claimsToSync)
{
var userClaim = userClaims
.FirstOrDefault(c => c.Type == addedClaim.Key);
if (info.Principal.HasClaim(c => c.Type == addedClaim.Key))
{
var externalClaim = info.Principal.FindFirst(addedClaim.Key);
if (userClaim == null)
{
await _userManager.AddClaimAsync(user,
new Claim(addedClaim.Key, externalClaim.Value));
refreshSignIn = true;
}
else if (userClaim.Value != externalClaim.Value)
{
await _userManager
.ReplaceClaimAsync(user, userClaim, externalClaim);
refreshSignIn = true;
}
}
else if (userClaim == null)
{
// Fill with a default value
await _userManager.AddClaimAsync(user, new Claim(addedClaim.Key,
addedClaim.Value));
refreshSignIn = true;
}
}
if (refreshSignIn)
{
await _signInManager.RefreshSignInAsync(user);
}
}
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
Uma abordagem semelhante é adotada quando as declarações mudam enquanto um usuário está conectado, mas uma etapa de preenchimento não é necessária. Para atualizar as declarações de um utilizador, execute o seguinte no utilizador:
- UserManager.ReplaceClaimAsync no usuário para declarações armazenadas no banco de dados de identidade.
- SignInManager.RefreshSignInAsync no usuário para forçar a geração de um novo cookiede autenticação.
Remover ações de reclamação e reivindicações
ClaimActionCollection.Remove(String) remove todas as ações de declaração para o ClaimType determinado da coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração específica de ClaimType da identidade. DeleteClaim é usado principalmente com OpenID Connect (OIDC) para remover reivindicações geradas por protocolo.
Exemplo de saída do aplicativo
Execute o aplicativo de exemplo
User Claims
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
someone@gmail.com
AspNet.Identity.SecurityStamp
7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
Judy
urn:google:locale
en
urn:google:picture
https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg
Authentication Properties
.Token.access_token
yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
Bearer
.Token.expires_at
2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
4/11/2019 9:14:52 PM
.TokenNames
access_token;token_type;expires_at;TicketCreated
.persistent
.issued
Thu, 11 Apr 2019 20:51:06 GMT
.expires
Thu, 25 Apr 2019 20:51:06 GMT
Encaminhar informações de solicitação com um proxy ou balanceador de carga
Se o aplicativo for implantado atrás de um servidor proxy ou balanceador de carga, algumas das informações de solicitação originais poderão ser encaminhadas para o aplicativo em cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), host e endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações de solicitação originais.
O esquema é usado na geração de links que afeta o fluxo de autenticação com provedores externos. Perder o esquema seguro (https
) leva a que a aplicação gere URLs incorretas de redirecionamento inseguro.
Use o middleware de cabeçalhos encaminhados para disponibilizar as informações de solicitação originais para o aplicativo para processamento de solicitações.
Para obter mais informações, consulte Configurar ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.
Um aplicativo ASP.NET Core pode estabelecer declarações e tokens adicionais de provedores de autenticação externos, como Facebook, Google, Microsoft e Twitter. Cada provedor revela informações diferentes sobre os usuários em sua plataforma, mas o padrão para receber e transformar dados do usuário em declarações adicionais é o mesmo.
Visualizar ou descarregar código de exemplo (como descarregar)
Pré-requisitos
Decida quais provedores de autenticação externos oferecer suporte no aplicativo. Para cada provedor, registre o aplicativo e obtenha um ID do cliente e um segredo do cliente. Para obter mais informações, consulte autenticação do Facebook e do Google no ASP.NET Core. A aplicação de exemplo usa o provedor de autenticação Google .
Definir o ID do cliente e o segredo do cliente
O provedor de autenticação OAuth estabelece uma relação de confiança com um aplicativo usando uma ID de cliente e segredo de cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externo quando o aplicativo é registrado no provedor. Cada fornecedor externo que a aplicação utiliza deve ser configurado independentemente com o ID do cliente e a chave secreta do cliente do fornecedor. Para obter mais informações, consulte os tópicos do provedor de autenticação externa que se aplicam ao seu cenário:
- autenticação do Facebook
- autenticação do Google
- autenticação Microsoft
- Autenticação do Twitter
- Outros provedores de autenticação
- OpenIdConnect
As declarações opcionais enviadas no ID ou token de acesso do provedor de autenticação geralmente são configuradas no portal online do provedor. Por exemplo, o Microsoft Entra ID permite que você atribua declarações opcionais ao token de ID do aplicativo na folha de configuração do
O aplicativo de exemplo configura o provedor de autenticação do Google com um ID do cliente e um segredo do cliente fornecidos pelo Google:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Estabelecer o escopo de autenticação
Especifique a lista de permissões a serem recuperadas do provedor especificando o Scope. Os escopos de autenticação para provedores externos comuns aparecem na tabela a seguir.
Fornecedor | Âmbito de aplicação |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , email , openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
No aplicativo de exemplo, os escopos profile
, email
e openid
do Google são adicionados automaticamente pela estrutura quando AddGoogle é chamado no AuthenticationBuilder. Se o aplicativo exigir escopos adicionais, adicione-os às opções. No exemplo a seguir, o escopo do Google https://www.googleapis.com/auth/user.birthday.read
é adicionado para recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapeie chaves de dados do usuário e crie declarações
Nas opções do provedor, especifique uma MapJsonKey ou MapJsonSubKey para cada chave/subchave nos dados JSON do usuário do provedor externo para que a identidade da aplicação seja lida durante o início de sessão. Para obter mais informações sobre tipos de declaração, consulte ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) a partir das chaves locale
e picture
nos dados do usuário do Google:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
No Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, um IdentityUser (ApplicationUser
) é conectado ao aplicativo com SignInAsync. Durante o processo de login, o UserManager<TUser> pode armazenar um ApplicationUser
declarações para dados do usuário disponíveis no Principal.
No aplicativo de exemplo, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) estabelece a configuração de localidade (urn:google:locale
) e imagem (urn:google:picture
) para o utilizador autenticado ApplicationUser
, incluindo uma declaração para GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
Por padrão, as alegações de um usuário são armazenadas na autenticação cookie. Se o cookie de autenticação for muito grande, pode fazer com que o aplicativo falhe porque:
- O navegador deteta que o cabeçalho cookie é muito longo.
- O tamanho total da solicitação é muito grande.
Se for necessária uma grande quantidade de dados do utilizador para processar os pedidos do utilizador:
- Limite o número e o tamanho das declarações de usuário para processamento de solicitações apenas ao que o aplicativo exige.
- Use um ITicketStore personalizado para o SessionStore do Middleware de Autenticação Cookie para armazenar identidade entre solicitações. Preserve grandes quantidades de informações de identidade no servidor enquanto envia apenas uma pequena chave de identificador de sessão para o cliente.
Salve o token de acesso
SaveTokens define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida.
SaveTokens
está definido como false
por padrão para reduzir o tamanho do cookiede autenticação final.
O aplicativo de exemplo define o valor de SaveTokens
como true
em GoogleOptions:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Quando OnPostConfirmationAsync
for executado, armazene o token de acesso (ExternalLoginInfo.AuthenticationTokens) do provedor externo no ApplicationUser
do AuthenticationProperties
.
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(registro de novo usuário) e OnGetCallbackAsync
(usuário registrado anteriormente) em Account/ExternalLogin.cshtml.cs
:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
Observação
Para obter informações sobre como passar tokens para os componentes Razor de uma aplicação Blazor do lado do servidor, consulte ASP.NET Core do lado do servidor e os cenários adicionais de segurança Blazor Web App.
Como adicionar tokens personalizados adicionais
Para demonstrar como adicionar um token personalizado, que é armazenado como parte do SaveTokens
, a aplicação de exemplo adiciona um AuthenticationToken com o DateTime atual para um AuthenticationToken.Name de TicketCreated
:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Criação e adição de declarações
A estrutura fornece ações comuns e métodos de extensão para criar e adicionar declarações à coleção. Para obter mais informações, consulte os ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método Run abstrato.
Para obter mais informações, consulte Microsoft.AspNetCore.Authentication.OAuth.Claims.
Adicionar e atualizar declarações de usuário
As declarações são copiadas de provedores externos para o banco de dados de usuários no primeiro registro, não no login. Se declarações adicionais forem habilitadas em um aplicativo depois que um usuário se registrar para usar o aplicativo, chame SignInManager.RefreshSignInAsync em um usuário para forçar a geração de um novo cookiede autenticação.
No ambiente de desenvolvimento que trabalha com contas de usuário de teste, você pode simplesmente excluir e recriar a conta de usuário. Para sistemas de produção, novas declarações adicionadas ao aplicativo podem ser preenchidas de volta em contas de usuário. Depois de andaime a página ExternalLogin
no aplicativo em Areas/Pages/Identity/Account/Manage
, adicione o seguinte código ao ExternalLoginModel
no arquivo ExternalLogin.cshtml.cs
.
Adicione um dicionário de declarações adicionadas. Use as chaves de dicionário para armazenar os tipos de reivindicações e use os valores para armazenar um valor padrão. Adicione a seguinte linha ao topo da classe. O exemplo a seguir pressupõe que seja adicionada uma declaração relativamente à imagem de perfil do Google do utilizador, com uma fotografia genérica de rosto como predefinição:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Substitua o código padrão do método OnGetCallbackAsync
pelo código a seguir. O código percorre o dicionário de declarações. As declarações são adicionadas (preenchidas) ou atualizadas para cada usuário. Quando as declarações são adicionadas ou atualizadas, o login do usuário é atualizado usando o SignInManager<TUser>, preservando as propriedades de autenticação existentes (AuthenticationProperties
).
public async Task<IActionResult> OnGetCallbackAsync(
string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a
// login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider,
info.ProviderKey, isPersistent: false, bypassTwoFactor : true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.",
info.Principal.Identity.Name, info.LoginProvider);
if (_claimsToSync.Count > 0)
{
var user = await _userManager.FindByLoginAsync(info.LoginProvider,
info.ProviderKey);
var userClaims = await _userManager.GetClaimsAsync(user);
bool refreshSignIn = false;
foreach (var addedClaim in _claimsToSync)
{
var userClaim = userClaims
.FirstOrDefault(c => c.Type == addedClaim.Key);
if (info.Principal.HasClaim(c => c.Type == addedClaim.Key))
{
var externalClaim = info.Principal.FindFirst(addedClaim.Key);
if (userClaim == null)
{
await _userManager.AddClaimAsync(user,
new Claim(addedClaim.Key, externalClaim.Value));
refreshSignIn = true;
}
else if (userClaim.Value != externalClaim.Value)
{
await _userManager
.ReplaceClaimAsync(user, userClaim, externalClaim);
refreshSignIn = true;
}
}
else if (userClaim == null)
{
// Fill with a default value
await _userManager.AddClaimAsync(user, new Claim(addedClaim.Key,
addedClaim.Value));
refreshSignIn = true;
}
}
if (refreshSignIn)
{
await _signInManager.RefreshSignInAsync(user);
}
}
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an
// account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
Uma abordagem semelhante é adotada quando as declarações mudam enquanto um usuário está conectado, mas uma etapa de preenchimento não é necessária. Para atualizar as declarações de um usuário, execute o seguinte para o usuário:
- UserManager.ReplaceClaimAsync no usuário para declarações armazenadas no banco de dados de identidade.
- SignInManager.RefreshSignInAsync no usuário para forçar a geração de um novo cookiede autenticação.
Remoção de ações de reclamação e reivindicações
ClaimActionCollection.Remove(String) remove todas as ações de reclamação para o ClaimType especificado na coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração do ClaimType determinado da identidade. DeleteClaim é usado principalmente com OpenID Connect (OIDC) para remover declarações geradas por protocolo.
Exemplo de saída do aplicativo
User Claims
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
someone@gmail.com
AspNet.Identity.SecurityStamp
7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
Judy
urn:google:locale
en
urn:google:picture
https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg
Authentication Properties
.Token.access_token
yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
Bearer
.Token.expires_at
2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
4/11/2019 9:14:52 PM
.TokenNames
access_token;token_type;expires_at;TicketCreated
.persistent
.issued
Thu, 11 Apr 2019 20:51:06 GMT
.expires
Thu, 25 Apr 2019 20:51:06 GMT
Encaminhar informações de solicitação com um proxy ou balanceador de carga
Se o aplicativo for implantado atrás de um servidor proxy ou balanceador de carga, algumas das informações de solicitação originais poderão ser encaminhadas para o aplicativo em cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), host e endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações de solicitação originais.
O esquema é usado na geração de links que afeta o fluxo de autenticação com provedores externos. Perder o esquema seguro (https
) resulta na aplicação gerando URLs incorretas de redirecionamento inseguro.
Use o middleware de cabeçalhos encaminhados para disponibilizar as informações de solicitação originais para o aplicativo para processamento de solicitações.
Para obter mais informações, consulte Configurar ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.
Um aplicativo ASP.NET Core pode estabelecer declarações e tokens adicionais de provedores de autenticação externos, como Facebook, Google, Microsoft e Twitter. Cada provedor revela informações diferentes sobre os usuários em sua plataforma, mas o padrão para receber e transformar dados do usuário em declarações adicionais é o mesmo.
Ver ou descarregar código de exemplo (como descarregar)
Pré-requisitos
Decida quais provedores de autenticação externos oferecer suporte no aplicativo. Para cada provedor, registre o aplicativo e obtenha um ID do cliente e um segredo do cliente. Para obter mais informações, consulte autenticação do Facebook e do Google no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação do Google.
Definir o ID do cliente e o segredo do cliente
O provedor de autenticação OAuth estabelece uma relação de confiança com um aplicativo usando uma ID de cliente e segredo de cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externo quando o aplicativo é registrado no provedor. Cada provedor externo que a aplicação utiliza deve ser configurado de forma independente com a identificação do cliente e o segredo do cliente do provedor. Para obter mais informações, consulte os tópicos do provedor de autenticação externa que se aplicam ao seu cenário:
- Autenticação do Facebook
- autenticação do Google
- autenticação da Microsoft
- autenticação do Twitter
- Outros provedores de autenticação
- OpenIdConnect
O aplicativo de exemplo configura o provedor de autenticação do Google com um ID do cliente e um segredo do cliente fornecidos pelo Google:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Estabelecer o escopo de autenticação
Especifique a lista de permissões a serem recuperadas do provedor especificando o Scope. Os escopos de autenticação para provedores externos comuns aparecem na tabela a seguir.
Fornecedor | Âmbito de aplicação |
---|---|
https://www.facebook.com/dialog/oauth |
|
https://www.googleapis.com/auth/userinfo.profile |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
Na aplicação de exemplo, o escopo de userinfo.profile
do Google é adicionado automaticamente pelo framework quando AddGoogle é chamado no AuthenticationBuilder. Se o aplicativo exigir escopos adicionais, adicione-os às opções. No exemplo a seguir, o escopo do Google https://www.googleapis.com/auth/user.birthday.read
é adicionado para recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapeie chaves de dados do usuário e crie declarações
Nas opções do provedor, especifique uma opção de MapJsonKey ou MapJsonSubKey para cada chave/subchave nos dados JSON do utilizador do provedor externo, permitindo que a identidade da aplicação leia essas informações ao fazer login. Para obter mais informações sobre tipos de declaração, consulte ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) a partir das chaves locale
e picture
nos dados do usuário do Google:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
No Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, um IdentityUser (ApplicationUser
) é conectado ao aplicativo com SignInAsync. Durante o processo de login, o UserManager<TUser> pode armazenar um ApplicationUser
declarações para dados do usuário disponíveis no Principal.
No aplicativo de exemplo, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) estabelece as declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) para o ApplicationUser
de entrada conectado, incluindo uma reivindicação para GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
Por padrão, as declarações de um usuário são armazenadas na autenticação cookie. Se o cookie de autenticação for muito grande, pode fazer com que o aplicativo falhe porque:
- O navegador deteta que o cabeçalho cookie é muito longo.
- O tamanho total da solicitação é muito grande.
Se for necessária uma grande quantidade de dados do utilizador para processar os pedidos do utilizador:
- Limite o número e o tamanho das declarações de usuário para processamento de solicitações apenas ao que o aplicativo exige.
- Use um ITicketStore personalizado para o SessionStore do Middleware de Autenticação Cookie para armazenar identidade entre solicitações. Preserve grandes quantidades de informações de identidade no servidor enquanto envia apenas uma pequena chave de identificador de sessão para o cliente.
Salve o token de acesso
SaveTokens define se os tokens de acesso e atualização devem ser armazenados no AuthenticationProperties após uma autorização bem-sucedida.
SaveTokens
está definido como false
por padrão para reduzir o tamanho do cookiede autenticação final.
O aplicativo de exemplo define o valor de SaveTokens
como true
em GoogleOptions:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Quando OnPostConfirmationAsync
executar, armazene o token de acesso (ExternalLoginInfo.AuthenticationTokens) do provedor externo no AuthenticationProperties
do ApplicationUser
.
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(registro de novo usuário) e OnGetCallbackAsync
(usuário registrado anteriormente) em Account/ExternalLogin.cshtml.cs
:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
Como adicionar tokens personalizados adicionais
Para demonstrar como adicionar um token personalizado, que é armazenado como parte do SaveTokens
, o aplicativo de exemplo adiciona um AuthenticationToken com o DateTime atual para um AuthenticationToken.Name de TicketCreated
:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
Criação e adição de declarações
A estrutura fornece ações comuns e métodos de extensão para criar e adicionar declarações à coleção. Para obter mais informações, consulte os ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método Run abstrato.
Para obter mais informações, consulte Microsoft.AspNetCore.Authentication.OAuth.Claims.
Remoção de ações de reclamação e reivindicações
ClaimActionCollection.Remove(String) remove todas as ações de declaração para o ClaimType determinado da coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração do ClaimType determinado da identidade. DeleteClaim é usado principalmente com o OpenID Connect (OIDC) para remover reivindicações geradas pelo protocolo.
Exemplo de saída do aplicativo
User Claims
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
someone@gmail.com
AspNet.Identity.SecurityStamp
7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
Judy
urn:google:locale
en
urn:google:picture
https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg
Authentication Properties
.Token.access_token
yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
Bearer
.Token.expires_at
2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
4/11/2019 9:14:52 PM
.TokenNames
access_token;token_type;expires_at;TicketCreated
.persistent
.issued
Thu, 11 Apr 2019 20:51:06 GMT
.expires
Thu, 25 Apr 2019 20:51:06 GMT
Encaminhar informações de solicitação com um proxy ou balanceador de carga
Se o aplicativo for implantado atrás de um servidor proxy ou balanceador de carga, algumas das informações de solicitação originais poderão ser encaminhadas para o aplicativo em cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), host e endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações de solicitação originais.
O esquema é usado na geração de links que afeta o fluxo de autenticação com provedores externos. Perder o esquema seguro (https
) resulta na aplicação gerando URLs de redirecionamento incorretos e inseguros.
Use o middleware de cabeçalhos encaminhados para disponibilizar as informações de solicitação originais para o aplicativo para processamento de solicitações.
Para obter mais informações, consulte Configurar ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.
Recursos adicionais
aplicativo de engenharia SocialSample dotnet/AspNetCore : O aplicativo de exemplo vinculado está na ramificação de engenhariado repositório dotnet/AspNetCore GitHub. A ramificação main
contém código em desenvolvimento ativo para a próxima versão do ASP.NET Core. Para ver uma versão do aplicativo de exemplo para uma versão lançada do ASP.NET Core, use a lista suspensa Branch para selecionar uma ramificação de versão (por exemplo,release/{X.Y}
).