Persistir 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 externa, 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 externa serão compatíveis com o aplicativo. Para cada provedor, registre o aplicativo e obtenha uma ID do cliente e um segredo do cliente. Para obter mais informações, confira Autenticação do Facebook e do Google no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação do Google.
Defina a 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 do cliente e um segredo do cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externa quando o aplicativo é registrado no provedor. Cada provedor externo que o aplicativo usa deve ser configurado de forma independente com a ID do cliente e o segredo do cliente do provedor. Para obter mais informações, confira os tópicos de provedor de autenticação externa que se aplicam:
- Autenticação do Facebook
- Autenticação do Google
- Autenticação da Microsoft
- Autenticação do Twitter
- Outros provedores de autenticação
- OpenIdConnect
As declarações opcionais enviadas na ID ou no 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 Configuração de token do registro do aplicativo. Para obter mais informações, confira Como fornecer declarações opcionais para seu aplicativo (documentação do Azure). Para outros provedores, confira os conjuntos de documentação externos.
O aplicativo de exemplo configura o provedor de autenticação do Google com uma 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.
Provedor | Escopo |
---|---|
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 https://www.googleapis.com/auth/user.birthday.read
do Google é adicionado para recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapear chaves de dados do usuário e criar declarações
Nas opções do provedor, especifique um MapJsonKey ou MapJsonSubKey para cada chave ou subchave nos dados do usuário JSON do provedor externo para que a identity do aplicativo seja lida no logon. Para obter mais informações sobre tipos de declarações, confira ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) das chaves locale
e picture
nos dados de 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 entrada, o UserManager<TUser> pode armazenar uma declaração ApplicationUser
para os 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
conectado, 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 usuário são armazenadas no cookie de autenticação. Se o cookie de autenticação for muito grande, isso poderá fazer com que o aplicativo falhe porque:
- O navegador detecta que o cabeçalho do cookie é muito longo.
- O tamanho geral da solicitação é muito grande.
Se uma grande quantidade de dados do usuário for necessária para processar solicitações do usuário:
- Limite o número e o tamanho das declarações de usuário para apenas processar as solicitações que o aplicativo requer.
- Use um ITicketStore personalizado para SessionStore do Middleware de Autenticação de Cookie armazenar a identity nas solicitações. Preserve grandes quantidades de informações de identity no servidor ao enviar 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
é definido como false
por padrão para reduzir o tamanho da autenticação cookie 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, armazene o token de acesso (ExternalLoginInfo.AuthenticationTokens) do provedor externo no ApplicationUser
.AuthenticationProperties
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(novo registro de 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 Razor componentes de um aplicativo do lado Blazor do servidor, consulte ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.
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
:
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, veja o ClaimActionCollectionMapExtensions e o ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método abstrato Run.
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 do usuário 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 uma nova autenticação cookie.
No Ambiente de desenvolvimento que trabalha com as 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 novamente em contas de usuário. Depois de fazer scaffolding da página ExternalLogin
no aplicativo em Areas/Pages/Identity/Account/Manage
, adicione o código a seguir ao ExternalLoginModel
no arquivo ExternalLogin.cshtml.cs
.
Adicione um dicionário de declarações adicionadas. Use as chaves de dicionário para manter os tipos de declaração e use os valores para armazenar um valor padrão. Adicione a seguinte linha no topo da classe. O exemplo a seguir pressupõe que uma declaração seja adicionada à foto do Google do usuário com uma imagem de headshot genérica como o 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 faz um loop pelo dicionário de declarações. As declarações são adicionadas (provisionadas) ou atualizadas para cada usuário. Quando as declarações são adicionadas ou atualizadas, a entrada do usuário é atualizada 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 são alteradas enquanto um usuário está conectado, mas uma etapa de preenchimento não é necessária. Para atualizar as declarações de um usuário, chame o seguinte no usuário:
- UserManager.ReplaceClaimAsync no usuário para declarações armazenadas no banco de dados de identity.
- SignInManager.RefreshSignInAsync no usuário para forçar a geração de uma nova autenticação cookie.
Remover ações de declaração e declarações
ClaimActionCollection.Remove(String) remove todas as ações de declaração para o ClaimType fornecido da coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração do ClaimType a partir da identity. DeleteClaim é usado principalmente com o OIDC (OpenID Connect) para remover declarações geradas por protocolo.
Exemplo de saída do aplicativo
Execute o aplicativo de exemplo e selecione o link MyClaims :
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 um balanceador de carga, algumas das informações da solicitação original podem ser encaminhadas para o aplicativo nos cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), o host e o endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações da solicitação original.
O esquema é usado na geração de link que afeta o fluxo de autenticação com provedores externos. Perder o esquema de seguro (https
) resulta no aplicativo gerando URLs de redirecionamento inseguros incorretos.
Use Middleware de cabeçalhos encaminhados para disponibilizar as informações da solicitação original ao aplicativo para o processamento da solicitação.
Para obter mais informações, veja Configurar o 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 externa, 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.
Exibir ou baixar código de exemplo (como baixar)
Pré-requisitos
Decida quais provedores de autenticação externa serão compatíveis com o aplicativo. Para cada provedor, registre o aplicativo e obtenha uma ID do cliente e um segredo do cliente. Para obter mais informações, confira Autenticação do Facebook e do Google no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação do Google.
Defina a 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 do cliente e um segredo do cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externa quando o aplicativo é registrado no provedor. Cada provedor externo que o aplicativo usa deve ser configurado de forma independente com a ID do cliente e o segredo do cliente do provedor. Para obter mais informações, confira os tópicos de 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
As declarações opcionais enviadas na ID ou no 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 Configuração de token do registro do aplicativo. Para obter mais informações, confira Como fornecer declarações opcionais para seu aplicativo (documentação do Azure). Para outros provedores, confira os conjuntos de documentação externos.
O aplicativo de exemplo configura o provedor de autenticação do Google com uma 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.
Provedor | Escopo |
---|---|
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 https://www.googleapis.com/auth/user.birthday.read
do Google é adicionado para recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapear chaves de dados do usuário e criar declarações
Nas opções do provedor, especifique um MapJsonKey ou MapJsonSubKey para cada chave/subchave nos dados do usuário JSON do provedor externo para que a identity do aplicativo seja lida no logon. Para obter mais informações sobre tipos de declarações, confira ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) das chaves locale
e picture
nos dados de 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 entrada, o UserManager<TUser> pode armazenar uma declaração ApplicationUser
para os 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
conectado, 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 declarações de um usuário são armazenadas no cookie de autenticação. Se o cookie de autenticação for muito grande, isso poderá fazer com que o aplicativo falhe porque:
- O navegador detecta que o cabeçalho do cookie é muito longo.
- O tamanho geral da solicitação é muito grande.
Se uma grande quantidade de dados do usuário for necessária para processar solicitações do usuário:
- Limite o número e o tamanho das declarações de usuário para apenas processar as solicitações que o aplicativo requer.
- Use um ITicketStore personalizado para SessionStore do Middleware de Autenticação de Cookie armazenar a identity nas solicitações. Preserve grandes quantidades de informações de identity no servidor ao enviar 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
é definido como false
por padrão para reduzir o tamanho da autenticação cookie 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
.AuthenticationProperties
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(novo registro de 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 Razor componentes de um aplicativo do lado Blazor do servidor, consulte ASP.NET Cenários de segurança adicionais e Blazor Web App do lado do servidor Core.
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;
};
});
Criando e adicionando 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, veja o ClaimActionCollectionMapExtensions e o ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método abstrato Run.
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 do usuário 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 uma nova autenticação cookie.
No Ambiente de desenvolvimento que trabalha com as contas de usuário de teste, basta excluir e recriar a conta de usuário. Para sistemas de produção, novas declarações adicionadas ao aplicativo podem ser preenchidas novamente em contas de usuário. Depois de fazer scaffolding da página ExternalLogin
no aplicativo em Areas/Pages/Identity/Account/Manage
, adicione o código a seguir ao ExternalLoginModel
no arquivo ExternalLogin.cshtml.cs
.
Adicione um dicionário de declarações adicionadas. Use as chaves de dicionário para manter os tipos de declaração e use os valores para armazenar um valor padrão. Adicione a seguinte linha no topo da classe. O exemplo a seguir pressupõe que uma declaração seja adicionada à foto do Google do usuário com uma imagem de headshot genérica como o 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 faz um loop pelo dicionário de declarações. As declarações são adicionadas (provisionadas) ou atualizadas para cada usuário. Quando as declarações são adicionadas ou atualizadas, a entrada do usuário é atualizada 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 são alteradas enquanto um usuário está conectado, mas uma etapa de preenchimento não é necessária. Para atualizar as declarações de um usuário, chame o seguinte no usuário:
- UserManager.ReplaceClaimAsync no usuário para declarações armazenadas no banco de dados de identity.
- SignInManager.RefreshSignInAsync no usuário para forçar a geração de uma nova autenticação cookie.
Remoção de ações de declaração e declarações
ClaimActionCollection.Remove(String) remove todas as ações de declaração para o ClaimType fornecido da coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração do ClaimType a partir da identity. DeleteClaim é usado principalmente com o OIDC (OpenID Connect) 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 um balanceador de carga, algumas das informações da solicitação original podem ser encaminhadas para o aplicativo nos cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), o host e o endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações da solicitação original.
O esquema é usado na geração de link que afeta o fluxo de autenticação com provedores externos. Perder o esquema de seguro (https
) resulta no aplicativo gerando URLs de redirecionamento inseguros incorretos.
Use Middleware de cabeçalhos encaminhados para disponibilizar as informações da solicitação original ao aplicativo para o processamento da solicitação.
Para obter mais informações, veja Configurar o 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 externa, 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.
Exibir ou baixar código de exemplo (como baixar)
Pré-requisitos
Decida quais provedores de autenticação externa serão compatíveis com o aplicativo. Para cada provedor, registre o aplicativo e obtenha uma ID do cliente e um segredo do cliente. Para obter mais informações, confira Autenticação do Facebook e do Google no ASP.NET Core. O aplicativo de exemplo usa o provedor de autenticação do Google.
Defina a 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 do cliente e um segredo do cliente. Os valores de ID do cliente e segredo do cliente são criados para o aplicativo pelo provedor de autenticação externa quando o aplicativo é registrado no provedor. Cada provedor externo que o aplicativo usa deve ser configurado de forma independente com a ID do cliente e o segredo do cliente do provedor. Para obter mais informações, confira os tópicos de 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 uma 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.
Provedor | Escopo |
---|---|
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 |
No aplicativo de exemplo, o escopo userinfo.profile
do Google é adicionado 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 https://www.googleapis.com/auth/user.birthday.read
do Google é adicionado a fim de recuperar o aniversário de um usuário:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapear chaves de dados do usuário e criar declarações
Nas opções do provedor, especifique um MapJsonKey ou MapJsonSubKey para cada chave/subchave nos dados do usuário JSON do provedor externo para que a identity do aplicativo seja lida no logon. Para obter mais informações sobre tipos de declarações, confira ClaimTypes.
O aplicativo de exemplo cria declarações de localidade (urn:google:locale
) e imagem (urn:google:picture
) das chaves locale
e picture
nos dados de 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 entrada, o UserManager<TUser> pode armazenar uma declaração ApplicationUser
para os 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
conectado, 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 declarações de um usuário são armazenadas no cookie de autenticação. Se o cookie de autenticação for muito grande, isso poderá fazer com que o aplicativo falhe porque:
- O navegador detecta que o cabeçalho do cookie é muito longo.
- O tamanho geral da solicitação é muito grande.
Se uma grande quantidade de dados do usuário for necessária para processar solicitações do usuário:
- Limite o número e o tamanho das declarações de usuário para apenas processar as solicitações que o aplicativo requer.
- Use um ITicketStore personalizado para SessionStore do Middleware de Autenticação de Cookie armazenar a identity nas solicitações. Preserve grandes quantidades de informações de identity no servidor ao enviar 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
é definido como false
por padrão para reduzir o tamanho da autenticação cookie 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
.AuthenticationProperties
O aplicativo de exemplo salva o token de acesso em OnPostConfirmationAsync
(novo registro de 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;
};
});
Criando e adicionando 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, veja o ClaimActionCollectionMapExtensions e o ClaimActionCollectionUniqueExtensions.
Os usuários podem definir ações personalizadas derivando de ClaimAction e implementando o método abstrato Run.
Para obter mais informações, consulte Microsoft.AspNetCore.Authentication.OAuth.Claims.
Remoção de ações de declaração e declarações
ClaimActionCollection.Remove(String) remove todas as ações de declaração para o ClaimType fornecido da coleção. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) exclui uma declaração do ClaimType a partir da identity. DeleteClaim é usado principalmente com o OIDC (OpenID Connect) 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 um balanceador de carga, algumas das informações da solicitação original podem ser encaminhadas para o aplicativo nos cabeçalhos de solicitação. Essas informações geralmente incluem o esquema de solicitação segura (https
), o host e o endereço IP do cliente. Os aplicativos não leem automaticamente esses cabeçalhos de solicitação para descobrir e usar as informações da solicitação original.
O esquema é usado na geração de link que afeta o fluxo de autenticação com provedores externos. Perder o esquema de seguro (https
) resulta no aplicativo gerando URLs de redirecionamento inseguros incorretos.
Use Middleware de cabeçalhos encaminhados para disponibilizar as informações da solicitação original ao aplicativo para o processamento da solicitação.
Para obter mais informações, veja Configurar o ASP.NET Core para trabalhar com servidores proxy e balanceadores de carga.
Recursos adicionais
- O aplicativo SocialSample de engenharia do dotnet/AspNetCore: o aplicativo de exemplo vinculado está no branch de engenharia
main
do repositório GitHub dotnet/AspNetCore. O branchmain
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 um branch de lançamento (por exemplorelease/{X.Y}
).