Condividi tramite


Rendere persistenti attestazioni e token aggiuntivi da provider esterni in ASP.NET Core

Un'app core ASP.NET può stabilire attestazioni e token aggiuntivi da provider di autenticazione esterni, ad esempio Facebook, Google, Microsoft e Twitter. Ogni provider rivela informazioni diverse sugli utenti sulla piattaforma, ma il modello per la ricezione e la trasformazione dei dati utente in attestazioni aggiuntive è lo stesso.

Prerequisiti

Decidere quali provider di autenticazione esterni supportare nell'app. Per ogni provider, registrare l'app e ottenere un ID client e un segreto client. Per altre informazioni, vedere Autenticazione di Facebook e Google in ASP.NET Core. L'app di esempio usa il provider di autenticazione Google.

Impostare l'ID client e il segreto client

Il provider di autenticazione OAuth stabilisce una relazione di trust con un'app usando un ID client e un segreto client. I valori di ID client e segreto client vengono creati per l'app dal provider di autenticazione esterno quando l'app viene registrata con il provider. Ogni provider esterno usato dall'app deve essere configurato in modo indipendente con l'ID client e il segreto client del provider. Per altre informazioni, vedere gli argomenti del provider di autenticazione esterno applicabili:

Le attestazioni facoltative inviate nell'ID o nel token di accesso dal provider di autenticazione vengono in genere configurate nel portale online del provider. Ad esempio, Microsoft Entra ID consente di assegnare attestazioni facoltative al token ID dell'app nel pannello configurazione token di registrazione dell'app. Per altre informazioni, vedere Procedura: Fornire attestazioni facoltative all'app (documentazione di Azure). Per altri provider, consultare i set di documentazione esterni.

L'app di esempio configura il provider di autenticazione Google con un ID client e un segreto client forniti da 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.

Stabilire l'ambito di autenticazione

Specificare l'elenco di autorizzazioni da recuperare dal provider specificando .Scope Gli ambiti di autenticazione per i provider esterni comuni vengono visualizzati nella tabella seguente.

Provider Ambito
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

Nell'app di esempio, gli ambiti , emaile openid di profileGoogle vengono aggiunti automaticamente dal framework quando AddGoogle viene chiamato su AuthenticationBuilder. Se l'app richiede ambiti aggiuntivi, aggiungerli alle opzioni. Nell'esempio seguente viene aggiunto l'ambito google https://www.googleapis.com/auth/user.birthday.read per recuperare il compleanno di un utente:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Eseguire il mapping delle chiavi dati utente e creare attestazioni

Nelle opzioni del provider specificare una MapJsonKey chiave o MapJsonSubKey per ogni chiave o sottochiave nei dati utente JSON del provider esterno per l'app per l'accesso identity . Per altre informazioni sui tipi di attestazione, vedere ClaimTypes.

L'app di esempio crea attestazioni locali () e immagine (urn:google:localeurn:google:picture) dalle locale chiavi e picture nei dati utente di 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;
    };
});

In Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncun oggetto IdentityUser (ApplicationUser) è connesso all'app con SignInAsync. Durante il processo di accesso, può UserManager<TUser> archiviare le ApplicationUser attestazioni per i dati utente disponibili da Principal.

Nell'app di esempio (OnPostConfirmationAsyncAccount/ExternalLogin.cshtml.cs) stabilisce le attestazioni delle impostazioni locali (urn:google:locale) e dell'immagine (urn:google:picture) per l'accessoApplicationUser, inclusa un'attestazione per 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();
}

Per impostazione predefinita, le attestazioni di un utente vengono archiviate nell'autenticazione cookie. Se l'autenticazione cookie è troppo grande, può causare un errore dell'app perché:

  • Il browser rileva che l'intestazione cookie è troppo lunga.
  • Le dimensioni complessive della richiesta sono troppo grandi.

Se per l'elaborazione delle richieste utente è necessaria una grande quantità di dati utente:

  • Limitare il numero e le dimensioni delle attestazioni utente per l'elaborazione delle richieste solo a ciò che l'app richiede.
  • Usare un oggetto personalizzato ITicketStore per il Cookie middleware di SessionStore autenticazione per archiviare identity tra le richieste. Mantenere grandi quantità di identity informazioni sul server inviando al client solo una piccola chiave dell'identificatore di sessione.

Salvare il token di accesso

SaveTokens definisce se i token di accesso e di aggiornamento devono essere archiviati in AuthenticationProperties dopo una corretta autorizzazione. SaveTokens è impostato su false per impostazione predefinita per ridurre le dimensioni dell'autenticazione cookiefinale.

L'app di esempio imposta il valore di SaveTokens su true in 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 viene eseguito, archiviare il token di accesso (ExternalLoginInfo.AuthenticationTokens) dal provider esterno in ApplicationUserAuthenticationProperties.

L'app di esempio salva il token di accesso in OnPostConfirmationAsync (nuova registrazione utente) e OnGetCallbackAsync (utente registrato in precedenza) in 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();
}

Nota

Per informazioni sul passaggio di token ai Razor componenti di un'app lato Blazor server, vedere Scenari di sicurezza aggiuntivi sul lato server ASP.NET CoreBlazor.

Come aggiungere altri token personalizzati

Per illustrare come aggiungere un token personalizzato, archiviato come parte di SaveTokens, l'app di esempio aggiunge un oggetto AuthenticationToken con l'oggetto corrente DateTime per un AuthenticationToken.Name di 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;
    };
});

Creare e aggiungere attestazioni

Il framework fornisce azioni comuni e metodi di estensione per la creazione e l'aggiunta di attestazioni alla raccolta. Per altre informazioni, vedere ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.

Gli utenti possono definire azioni personalizzate derivando e ClaimAction implementando il metodo astratto Run .

Per ulteriori informazioni, vedere Microsoft.AspNetCore.Authentication.OAuth.Claims.

Aggiungere e aggiornare le attestazioni utente

Le attestazioni vengono copiate da provider esterni al database utente alla prima registrazione, non all'accesso. Se in un'app sono abilitate attestazioni aggiuntive dopo che un utente si registra per l'uso dell'app, chiamare SignInManager.RefreshSignInAsync su un utente per forzare la generazione di una nuova autenticazione cookie.

Nell'ambiente di sviluppo che usa gli account utente di test eliminare e ricreare l'account utente. Per i sistemi di produzione, le nuove attestazioni aggiunte all'app possono essere riempite negli account utente. Dopo aver eseguito lo scaffolding della ExternalLogin pagina nell'app in Areas/Pages/Identity/Account/Manage, aggiungere il codice seguente a ExternalLoginModel nel ExternalLogin.cshtml.cs file .

Aggiungere un dizionario di attestazioni aggiunte. Usare le chiavi del dizionario per contenere i tipi di attestazione e usare i valori per contenere un valore predefinito. Aggiungere la riga seguente all'inizio della classe. L'esempio seguente presuppone che venga aggiunta un'attestazione per l'immagine Google dell'utente con un'immagine headshot generica come valore predefinito:

private readonly IReadOnlyDictionary<string, string> _claimsToSync =
     new Dictionary<string, string>()
     {
             { "urn:google:picture", "https://localhost:5001/headshot.png" },
     };

Sostituire il codice predefinito del OnGetCallbackAsync metodo con il codice seguente. Il codice scorre il dizionario delle attestazioni. Le attestazioni vengono aggiunte (riempite) o aggiornate per ogni utente. Quando le attestazioni vengono aggiunte o aggiornate, l'accesso utente viene aggiornato usando , SignInManager<TUser>mantenendo le proprietà di autenticazione esistenti (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();
    }
}

Un approccio simile viene adottato quando le attestazioni cambiano mentre un utente ha eseguito l'accesso, ma non è necessario un passaggio di backfill. Per aggiornare le attestazioni di un utente, chiamare quanto segue sull'utente:

Rimuovere le azioni e le attestazioni attestazioni

ClaimActionCollection.Remove(String) rimuove tutte le azioni attestazioni per l'oggetto specificato ClaimType dalla raccolta. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) elimina un'attestazione dell'oggetto identityspecificato ClaimType da . DeleteClaim viene usato principalmente con OpenID Connect (OIDC) per rimuovere le attestazioni generate dal protocollo.

Output dell'app di esempio

Eseguire l'app di esempio e selezionare il collegamento 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

Inoltrare informazioni della richiesta con un proxy o un servizio di bilanciamento del carico

Se l'app viene distribuita dietro un server proxy o un servizio di bilanciamento del carico, alcune delle informazioni della richiesta originale possono essere inoltrate all'app nelle intestazioni della richiesta. Queste informazioni includono in genere lo schema della richiesta sicura (https), l'host e l'indirizzo IP del client. Le app non leggono automaticamente queste intestazioni della richiesta per individuare e usare le informazioni della richiesta originale.

Lo schema viene usato nella generazione di collegamenti che influisce sul flusso di autenticazione con provider esterni. La perdita dello schema sicuro (https) fa sì che l'app generi URL di reindirizzamento non sicuri e non corretti.

Usare il middleware delle intestazioni inoltrate per rendere disponibili per l'app le informazioni della richiesta originale per l'elaborazione delle richieste.

Per altre informazioni, vedere Configurare ASP.NET Core per l'utilizzo di server proxy e servizi di bilanciamento del carico.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Un'app core ASP.NET può stabilire attestazioni e token aggiuntivi da provider di autenticazione esterni, ad esempio Facebook, Google, Microsoft e Twitter. Ogni provider rivela informazioni diverse sugli utenti sulla piattaforma, ma il modello per la ricezione e la trasformazione dei dati utente in attestazioni aggiuntive è lo stesso.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Prerequisiti

Decidere quali provider di autenticazione esterni supportare nell'app. Per ogni provider, registrare l'app e ottenere un ID client e un segreto client. Per altre informazioni, vedere Autenticazione di Facebook e Google in ASP.NET Core. L'app di esempio usa il provider di autenticazione Google.

Impostare l'ID client e il segreto client

Il provider di autenticazione OAuth stabilisce una relazione di trust con un'app usando un ID client e un segreto client. I valori di ID client e segreto client vengono creati per l'app dal provider di autenticazione esterno quando l'app viene registrata con il provider. Ogni provider esterno usato dall'app deve essere configurato in modo indipendente con l'ID client e il segreto client del provider. Per altre informazioni, vedere gli argomenti del provider di autenticazione esterno applicabili allo scenario:

Le attestazioni facoltative inviate nell'ID o nel token di accesso dal provider di autenticazione vengono in genere configurate nel portale online del provider. Ad esempio, Microsoft Entra ID consente di assegnare attestazioni facoltative al token ID dell'app nel pannello di configurazione del token di registrazione dell'app. Per altre informazioni, vedere Procedura: Fornire attestazioni facoltative all'app (documentazione di Azure). Per altri provider, consultare i set di documentazione esterni.

L'app di esempio configura il provider di autenticazione Google con un ID client e un segreto client forniti da 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;
    };
});

Stabilire l'ambito di autenticazione

Specificare l'elenco di autorizzazioni da recuperare dal provider specificando .Scope Gli ambiti di autenticazione per i provider esterni comuni vengono visualizzati nella tabella seguente.

Provider Ambito
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

Nell'app di esempio, gli ambiti , emaile openid di profileGoogle vengono aggiunti automaticamente dal framework quando AddGoogle viene chiamato su AuthenticationBuilder. Se l'app richiede ambiti aggiuntivi, aggiungerli alle opzioni. Nell'esempio seguente viene aggiunto l'ambito google https://www.googleapis.com/auth/user.birthday.read per recuperare il compleanno di un utente:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Eseguire il mapping delle chiavi dati utente e creare attestazioni

Nelle opzioni del provider specificare un MapJsonKey o MapJsonSubKey per ogni chiave/sottochiave nei dati utente JSON del provider esterno per l'app per l'accesso identity . Per altre informazioni sui tipi di attestazione, vedere ClaimTypes.

L'app di esempio crea attestazioni locali () e immagine (urn:google:localeurn:google:picture) dalle locale chiavi e picture nei dati utente di 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;
    };
});

In Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncun oggetto IdentityUser (ApplicationUser) è connesso all'app con SignInAsync. Durante il processo di accesso, può UserManager<TUser> archiviare le ApplicationUser attestazioni per i dati utente disponibili da Principal.

Nell'app di esempio (OnPostConfirmationAsyncAccount/ExternalLogin.cshtml.cs) stabilisce le attestazioni delle impostazioni locali (urn:google:locale) e dell'immagine (urn:google:picture) per l'accessoApplicationUser, inclusa un'attestazione per 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();
}

Per impostazione predefinita, le attestazioni di un utente vengono archiviate nell'autenticazione cookie. Se l'autenticazione cookie è troppo grande, può causare un errore dell'app perché:

  • Il browser rileva che l'intestazione cookie è troppo lunga.
  • Le dimensioni complessive della richiesta sono troppo grandi.

Se per l'elaborazione delle richieste utente è necessaria una grande quantità di dati utente:

  • Limitare il numero e le dimensioni delle attestazioni utente per l'elaborazione delle richieste solo a ciò che l'app richiede.
  • Usare un oggetto personalizzato ITicketStore per il Cookie middleware di SessionStore autenticazione per archiviare identity tra le richieste. Mantenere grandi quantità di identity informazioni sul server inviando al client solo una piccola chiave dell'identificatore di sessione.

Salvare il token di accesso

SaveTokens definisce se i token di accesso e di aggiornamento devono essere archiviati in AuthenticationProperties dopo una corretta autorizzazione. SaveTokens è impostato su false per impostazione predefinita per ridurre le dimensioni dell'autenticazione cookiefinale.

L'app di esempio imposta il valore di SaveTokens su true in 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 viene eseguito, archiviare il token di accesso (ExternalLoginInfo.AuthenticationTokens) dal provider esterno in ApplicationUserAuthenticationProperties.

L'app di esempio salva il token di accesso in OnPostConfirmationAsync (nuova registrazione utente) e OnGetCallbackAsync (utente registrato in precedenza) in 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();
}

Nota

Per informazioni sul passaggio di token ai Razor componenti di un'app lato Blazor server, vedere Scenari di sicurezza aggiuntivi sul lato server ASP.NET CoreBlazor.

Come aggiungere altri token personalizzati

Per illustrare come aggiungere un token personalizzato, archiviato come parte di SaveTokens, l'app di esempio aggiunge un oggetto AuthenticationToken con l'oggetto corrente DateTime per un AuthenticationToken.Name di 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;
    };
});

Creazione e aggiunta di attestazioni

Il framework fornisce azioni comuni e metodi di estensione per la creazione e l'aggiunta di attestazioni alla raccolta. Per altre informazioni, vedere ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.

Gli utenti possono definire azioni personalizzate derivando e ClaimAction implementando il metodo astratto Run .

Per ulteriori informazioni, vedere Microsoft.AspNetCore.Authentication.OAuth.Claims.

Aggiungere e aggiornare le attestazioni utente

Le attestazioni vengono copiate da provider esterni al database utente alla prima registrazione, non all'accesso. Se in un'app sono abilitate attestazioni aggiuntive dopo che un utente si registra per l'uso dell'app, chiamare SignInManager.RefreshSignInAsync su un utente per forzare la generazione di una nuova autenticazione cookie.

Nell'ambiente di sviluppo che usa gli account utente di test è sufficiente eliminare e ricreare l'account utente. Per i sistemi di produzione, le nuove attestazioni aggiunte all'app possono essere riempite negli account utente. Dopo aver eseguito lo scaffolding della ExternalLogin pagina nell'app in Areas/Pages/Identity/Account/Manage, aggiungere il codice seguente a ExternalLoginModel nel ExternalLogin.cshtml.cs file .

Aggiungere un dizionario di attestazioni aggiunte. Usare le chiavi del dizionario per contenere i tipi di attestazione e usare i valori per contenere un valore predefinito. Aggiungere la riga seguente all'inizio della classe. L'esempio seguente presuppone che venga aggiunta un'attestazione per l'immagine Google dell'utente con un'immagine headshot generica come valore predefinito:

private readonly IReadOnlyDictionary<string, string> _claimsToSync = 
    new Dictionary<string, string>()
    {
        { "urn:google:picture", "https://localhost:5001/headshot.png" },
    };

Sostituire il codice predefinito del OnGetCallbackAsync metodo con il codice seguente. Il codice scorre il dizionario delle attestazioni. Le attestazioni vengono aggiunte (riempite) o aggiornate per ogni utente. Quando le attestazioni vengono aggiunte o aggiornate, l'accesso utente viene aggiornato usando , SignInManager<TUser>mantenendo le proprietà di autenticazione esistenti (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();
    }
}

Un approccio simile viene adottato quando le attestazioni cambiano mentre un utente ha eseguito l'accesso, ma non è necessario un passaggio di backfill. Per aggiornare le attestazioni di un utente, chiamare quanto segue sull'utente:

Rimozione di azioni e attestazioni attestazioni

ClaimActionCollection.Remove(String) rimuove tutte le azioni attestazioni per l'oggetto specificato ClaimType dalla raccolta. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) elimina un'attestazione dell'oggetto identityspecificato ClaimType da . DeleteClaim viene usato principalmente con OpenID Connect (OIDC) per rimuovere le attestazioni generate dal protocollo.

Output dell'app di esempio

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

Inoltrare informazioni della richiesta con un proxy o un servizio di bilanciamento del carico

Se l'app viene distribuita dietro un server proxy o un servizio di bilanciamento del carico, alcune delle informazioni della richiesta originale possono essere inoltrate all'app nelle intestazioni della richiesta. Queste informazioni includono in genere lo schema della richiesta sicura (https), l'host e l'indirizzo IP del client. Le app non leggono automaticamente queste intestazioni della richiesta per individuare e usare le informazioni della richiesta originale.

Lo schema viene usato nella generazione di collegamenti che influisce sul flusso di autenticazione con provider esterni. La perdita dello schema sicuro (https) fa sì che l'app generi URL di reindirizzamento non sicuri e non corretti.

Usare il middleware delle intestazioni inoltrate per rendere disponibili per l'app le informazioni della richiesta originale per l'elaborazione delle richieste.

Per altre informazioni, vedere Configurare ASP.NET Core per l'utilizzo di server proxy e servizi di bilanciamento del carico.

Un'app core ASP.NET può stabilire attestazioni e token aggiuntivi da provider di autenticazione esterni, ad esempio Facebook, Google, Microsoft e Twitter. Ogni provider rivela informazioni diverse sugli utenti sulla piattaforma, ma il modello per la ricezione e la trasformazione dei dati utente in attestazioni aggiuntive è lo stesso.

Visualizzare o scaricare il codice di esempio (procedura per il download)

Prerequisiti

Decidere quali provider di autenticazione esterni supportare nell'app. Per ogni provider, registrare l'app e ottenere un ID client e un segreto client. Per altre informazioni, vedere Autenticazione di Facebook e Google in ASP.NET Core. L'app di esempio usa il provider di autenticazione Google.

Impostare l'ID client e il segreto client

Il provider di autenticazione OAuth stabilisce una relazione di trust con un'app usando un ID client e un segreto client. I valori di ID client e segreto client vengono creati per l'app dal provider di autenticazione esterno quando l'app viene registrata con il provider. Ogni provider esterno usato dall'app deve essere configurato in modo indipendente con l'ID client e il segreto client del provider. Per altre informazioni, vedere gli argomenti del provider di autenticazione esterno applicabili allo scenario:

L'app di esempio configura il provider di autenticazione Google con un ID client e un segreto client forniti da 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;
    };
});

Stabilire l'ambito di autenticazione

Specificare l'elenco di autorizzazioni da recuperare dal provider specificando .Scope Gli ambiti di autenticazione per i provider esterni comuni vengono visualizzati nella tabella seguente.

Provider Ambito
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

Nell'app di esempio, l'ambito di userinfo.profile Google viene aggiunto automaticamente dal framework quando AddGoogle viene chiamato su AuthenticationBuilder. Se l'app richiede ambiti aggiuntivi, aggiungerli alle opzioni. Nell'esempio seguente viene aggiunto l'ambito google https://www.googleapis.com/auth/user.birthday.read per recuperare il compleanno di un utente:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Eseguire il mapping delle chiavi dati utente e creare attestazioni

Nelle opzioni del provider specificare un MapJsonKey o MapJsonSubKey per ogni chiave/sottochiave nei dati utente JSON del provider esterno per l'app per l'accesso identity . Per altre informazioni sui tipi di attestazione, vedere ClaimTypes.

L'app di esempio crea attestazioni locali () e immagine (urn:google:localeurn:google:picture) dalle locale chiavi e picture nei dati utente di 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;
    };
});

In Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncun oggetto IdentityUser (ApplicationUser) è connesso all'app con SignInAsync. Durante il processo di accesso, può UserManager<TUser> archiviare le ApplicationUser attestazioni per i dati utente disponibili da Principal.

Nell'app di esempio (OnPostConfirmationAsyncAccount/ExternalLogin.cshtml.cs) stabilisce le attestazioni delle impostazioni locali (urn:google:locale) e dell'immagine (urn:google:picture) per l'accessoApplicationUser, inclusa un'attestazione per 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();
}

Per impostazione predefinita, le attestazioni di un utente vengono archiviate nell'autenticazione cookie. Se l'autenticazione cookie è troppo grande, può causare un errore dell'app perché:

  • Il browser rileva che l'intestazione cookie è troppo lunga.
  • Le dimensioni complessive della richiesta sono troppo grandi.

Se per l'elaborazione delle richieste utente è necessaria una grande quantità di dati utente:

  • Limitare il numero e le dimensioni delle attestazioni utente per l'elaborazione delle richieste solo a ciò che l'app richiede.
  • Usare un oggetto personalizzato ITicketStore per il Cookie middleware di SessionStore autenticazione per archiviare identity tra le richieste. Mantenere grandi quantità di identity informazioni sul server inviando al client solo una piccola chiave dell'identificatore di sessione.

Salvare il token di accesso

SaveTokens definisce se i token di accesso e di aggiornamento devono essere archiviati in AuthenticationProperties dopo una corretta autorizzazione. SaveTokens è impostato su false per impostazione predefinita per ridurre le dimensioni dell'autenticazione cookiefinale.

L'app di esempio imposta il valore di SaveTokens su true in 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 viene eseguito, archiviare il token di accesso (ExternalLoginInfo.AuthenticationTokens) dal provider esterno in ApplicationUserAuthenticationProperties.

L'app di esempio salva il token di accesso in OnPostConfirmationAsync (nuova registrazione utente) e OnGetCallbackAsync (utente registrato in precedenza) in 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();
}

Come aggiungere altri token personalizzati

Per illustrare come aggiungere un token personalizzato, archiviato come parte di SaveTokens, l'app di esempio aggiunge un oggetto AuthenticationToken con l'oggetto corrente DateTime per un AuthenticationToken.Name di 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;
    };
});

Creazione e aggiunta di attestazioni

Il framework fornisce azioni comuni e metodi di estensione per la creazione e l'aggiunta di attestazioni alla raccolta. Per altre informazioni, vedere ClaimActionCollectionMapExtensions e ClaimActionCollectionUniqueExtensions.

Gli utenti possono definire azioni personalizzate derivando e ClaimAction implementando il metodo astratto Run .

Per ulteriori informazioni, vedere Microsoft.AspNetCore.Authentication.OAuth.Claims.

Rimozione di azioni e attestazioni attestazioni

ClaimActionCollection.Remove(String) rimuove tutte le azioni attestazioni per l'oggetto specificato ClaimType dalla raccolta. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) elimina un'attestazione dell'oggetto identityspecificato ClaimType da . DeleteClaim viene usato principalmente con OpenID Connect (OIDC) per rimuovere le attestazioni generate dal protocollo.

Output dell'app di esempio

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

Inoltrare informazioni della richiesta con un proxy o un servizio di bilanciamento del carico

Se l'app viene distribuita dietro un server proxy o un servizio di bilanciamento del carico, alcune delle informazioni della richiesta originale possono essere inoltrate all'app nelle intestazioni della richiesta. Queste informazioni includono in genere lo schema della richiesta sicura (https), l'host e l'indirizzo IP del client. Le app non leggono automaticamente queste intestazioni della richiesta per individuare e usare le informazioni della richiesta originale.

Lo schema viene usato nella generazione di collegamenti che influisce sul flusso di autenticazione con provider esterni. La perdita dello schema sicuro (https) fa sì che l'app generi URL di reindirizzamento non sicuri e non corretti.

Usare il middleware delle intestazioni inoltrate per rendere disponibili per l'app le informazioni della richiesta originale per l'elaborazione delle richieste.

Per altre informazioni, vedere Configurare ASP.NET Core per l'utilizzo di server proxy e servizi di bilanciamento del carico.

Risorse aggiuntive

  • App SocialSample dotnet/AspNetCore engineering: l'app di esempio collegata si trova nel ramo di progettazione di main GitHub dotnet/AspNetCore. Il main ramo contiene codice in fase di sviluppo attivo per la versione successiva di ASP.NET Core. Per visualizzare una versione dell'app di esempio per una versione rilasciata di ASP.NET Core, usare l'elenco a discesa Branch per selezionare un ramo di rilascio ( ad esempio release/{X.Y}).