Partilhar via


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:

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 Token do registro do aplicativo. Para obter mais informações, consulte Como fornecer declarações opcionais ao seu aplicativo (documentação do Azure). Para outros fornecedores, consulte seus conjuntos de documentação externa.

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
Facebook https://www.facebook.com/dialog/oauth
Google profile, email, openid
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate

No aplicativo de exemplo, os escopos profile, emaile 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 AuthenticationPropertiesdo 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:

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 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 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.

Exibir ou baixar código de amostra (como baixar)

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:

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 Token do registro do aplicativo. Para obter mais informações, consulte Como fornecer declarações opcionais ao seu aplicativo (documentação do Azure). Para outros fornecedores, consulte seus conjuntos de documentação externa.

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
Facebook https://www.facebook.com/dialog/oauth
Google profile, email, openid
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate

No aplicativo de exemplo, os escopos profile, emaile 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 ApplicationUserdo 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:

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:

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
Facebook https://www.facebook.com/dialog/oauth
Google https://www.googleapis.com/auth/userinfo.profile
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter 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 ApplicationUserde 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 AuthenticationPropertiesdo 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 engenharia do 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}).