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:
- Autenticazione di Facebook
- Autenticazione di Google
- Autenticazione Microsoft
- Autenticazione di Twitter
- Altri provider di autenticazione
- OpenIdConnect
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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , email , openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
Nell'app di esempio, gli ambiti , email
e openid
di profile
Google 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:locale
urn: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.OnPostConfirmationAsync
un 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 (OnPostConfirmationAsync
Account/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 ApplicationUser
AuthenticationProperties
.
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 ASP.NET Scenari di sicurezza aggiuntivi e Blazor Web App lato server Core.
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:
- UserManager.ReplaceClaimAsync sull'utente per le attestazioni archiviate nel identity database.
- SignInManager.RefreshSignInAsync sull'utente per forzare la generazione di una nuova autenticazione cookie.
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:
- Autenticazione di Facebook
- Autenticazione di Google
- Autenticazione Microsoft
- Autenticazione di Twitter
- Altri provider di autenticazione
- OpenIdConnect
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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , email , openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
Nell'app di esempio, gli ambiti , email
e openid
di profile
Google 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:locale
urn: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.OnPostConfirmationAsync
un 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 (OnPostConfirmationAsync
Account/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 ApplicationUser
AuthenticationProperties
.
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 ASP.NET Scenari di sicurezza aggiuntivi e Blazor Web App lato server Core.
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:
- UserManager.ReplaceClaimAsync sull'utente per le attestazioni archiviate nel identity database.
- SignInManager.RefreshSignInAsync sull'utente per forzare la generazione di una nuova autenticazione cookie.
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:
- Autenticazione di Facebook
- Autenticazione di Google
- Autenticazione Microsoft
- Autenticazione di Twitter
- Altri provider di autenticazione
- OpenIdConnect
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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
https://www.googleapis.com/auth/userinfo.profile |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
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:locale
urn: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.OnPostConfirmationAsync
un 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 (OnPostConfirmationAsync
Account/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 ApplicationUser
AuthenticationProperties
.
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. Ilmain
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 esempiorelease/{X.Y}
).