Aanvullende claims en tokens van externe providers behouden in ASP.NET Core
Een ASP.NET Core-app kan aanvullende claims en tokens van externe verificatieproviders tot stand brengen, zoals Facebook, Google, Microsoft en Twitter. Elke provider toont verschillende informatie over gebruikers op het platform, maar het patroon voor het ontvangen en transformeren van gebruikersgegevens in aanvullende claims is hetzelfde.
Voorwaarden
Bepaal welke externe verificatieproviders in de app moeten worden ondersteund. Registreer de app voor elke provider en haal een client-id en clientgeheim op. Zie Facebook- en Google-verificatie in ASP.NET Corevoor meer informatie. De voorbeeld-app maakt gebruik van de Google-verificatieprovider.
De client-id en het clientgeheim instellen
De OAuth-verificatieprovider brengt een vertrouwensrelatie tot stand met een app met behulp van een client-id en clientgeheim. Client-id- en clientgeheimwaarden worden gemaakt voor de app door de externe verificatieprovider wanneer de app is geregistreerd bij de provider. Elke externe provider die door de app wordt gebruikt, moet onafhankelijk worden geconfigureerd met de client-id en het clientgeheim van de provider. Zie de onderwerpen voor externe verificatieproviders die van toepassing zijn voor meer informatie:
- Facebook-verificatie
- Google-verificatie
- Microsoft-verificatie
- Twitter-verificatie
- Andere verificatieproviders
- OpenIdConnect
Optionele claims die worden verzonden in de ID of het toegangstoken van de authenticatieprovider, worden meestal geconfigureerd in de online portal van de provider. Microsoft Entra ID staat bijvoorbeeld toe dat er optionele claims worden toegewezen aan het id-token van de app in het blad van de tokenconfiguratie
De voorbeeld-app configureert de Google-verificatieprovider met een client-id en clientgeheim dat wordt geleverd door 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.
Het verificatiebereik instellen
Geef de lijst met machtigingen op die moeten worden opgehaald van de provider door de Scopeop te geven. Verificatiebereiken voor algemene externe providers worden weergegeven in de volgende tabel.
Aanbieder | Draagwijdte |
---|---|
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 |
In de voorbeeldapp worden de Google-scopes profile
, email
en openid
automatisch toegevoegd door het framework wanneer AddGoogle wordt aangeroepen op de AuthenticationBuilder. Als de app extra toestemmingen vereist, voeg deze toe aan de opties. In het volgende voorbeeld wordt de Google https://www.googleapis.com/auth/user.birthday.read
scope toegevoegd om de verjaardag van een gebruiker te verkrijgen.
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Gebruikersgegevenssleutels afstemmen en claims toekennen
Geef in de opties van de provider een MapJsonKey of MapJsonSubKey op voor elke sleutel of subsleutel in de JSON-gebruikersgegevens van de externe provider, zodat de app-identiteit deze kan lezen bij het aanmelden. Zie ClaimTypesvoor meer informatie over claimtypen.
De voorbeeld-app maakt landinstellingen (urn:google:locale
) en afbeeldingsclaims (urn:google:picture
) op basis van de locale
- en picture
-sleutels in Google-gebruikersgegevens:
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
wordt met SignInAsynceen IdentityUser (ApplicationUser
) aangemeld bij de app. Tijdens het aanmeldingsproces kan de UserManager<TUser> meerdere ApplicationUser
-claims opslaan voor gebruikersgegevens die beschikbaar zijn via de Principal.
In de voorbeeld-app brengt OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) de landinstelling (urn:google:locale
) en afbeeldingsclaims (urn:google:picture
) vast voor de aangemelde ApplicationUser
, inclusief een claim voor 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();
}
De claims van een gebruiker worden standaard opgeslagen in de verificatie cookie. Als de verificatie cookie te groot is, kan dit ertoe leiden dat de app mislukt omdat:
- De browser detecteert dat de cookie header te lang is.
- De totale grootte van de aanvraag is te groot.
Als er een grote hoeveelheid gebruikersgegevens is vereist voor het verwerken van gebruikersaanvragen:
- Beperk het aantal en de grootte van gebruikersclaims voor aanvraagverwerking tot alleen wat de app nodig heeft.
- Gebruik een aangepaste ITicketStore voor de Cookie Authentication Middleware's SessionStore om identiteit op te slaan in aanvragen. Bewaar grote hoeveelheden identiteitsgegevens op de server terwijl alleen een kleine sessie-id-sleutel naar de client wordt verzonden.
Het toegangstoken opslaan
SaveTokens bepaalt of toegangs- en vernieuwingstokens na een geslaagde autorisatie moeten worden opgeslagen in de AuthenticationProperties.
SaveTokens
is standaard ingesteld op false
om de grootte van de uiteindelijke verificatie cookiete verkleinen.
Met de voorbeeld-app wordt de waarde van SaveTokens
ingesteld op 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;
};
});
Wanneer OnPostConfirmationAsync
wordt uitgevoerd, slaat u het toegangstoken (ExternalLoginInfo.AuthenticationTokens) van de externe provider op in de ApplicationUser
's AuthenticationProperties
.
De voorbeeld-app slaat het toegangstoken op in OnPostConfirmationAsync
(nieuwe gebruikersregistratie) en OnGetCallbackAsync
(eerder geregistreerde gebruiker) 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();
}
Notitie
Zie ASP.NET Core-server en Blazor Web App aanvullende beveiligingsscenario'svoor informatie over het doorgeven van tokens aan de Razor onderdelen van een Blazor-app aan de serverzijde.
Aanvullende aangepaste tokens toevoegen
Om te laten zien hoe u een aangepast token toevoegt, dat is opgeslagen als onderdeel van SaveTokens
, voegt de voorbeeld-app een AuthenticationToken toe met de huidige DateTime voor een AuthenticationToken.Name van 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;
};
});
Claims maken en toevoegen
Het framework biedt algemene acties en uitbreidingsmethoden voor het maken en toevoegen van claims aan de verzameling. Zie de ClaimActionCollectionMapExtensions en ClaimActionCollectionUniqueExtensionsvoor meer informatie.
Gebruikers kunnen aangepaste acties definiëren door af te leiden van ClaimAction en de abstracte methode Run te implementeren.
Zie Microsoft.AspNetCore.Authentication.OAuth.Claimsvoor meer informatie.
Gebruikersclaims toevoegen en bijwerken
Claims worden gekopieerd van externe providers naar de gebruikersdatabase bij de eerste registratie, niet bij aanmelding. Als extra claims zijn ingeschakeld in een app nadat een gebruiker zich heeft geregistreerd om de app te gebruiken, roept u SignInManager.RefreshSignInAsync- aan op een gebruiker om het genereren van een nieuwe verificatie af te dwingen cookie.
Verwijder en maak het gebruikersaccount opnieuw in de ontwikkelomgeving die werkt met testgebruikersaccounts. Voor productiesystemen kunnen nieuwe claims die aan de app worden toegevoegd, worden ingevuld in gebruikersaccounts. Nadat de ExternalLogin
pagina in de app op Areas/Pages/Identity/Account/Manage
hebt geplaatst, voegt u de volgende code toe aan de ExternalLoginModel
in het ExternalLogin.cshtml.cs
-bestand.
Voeg een woordenlijst met toegevoegde claims toe. Gebruik de woordenlijstsleutels om de claimtypen op te slaan en gebruik de waarden om een standaardwaarde op te slaan. Voeg de volgende regel toe aan het begin van de klasse. In het volgende voorbeeld wordt ervan uitgegaan dat er één claim wordt toegevoegd voor de Google-afbeelding van de gebruiker met een algemene headshotafbeelding als de standaardwaarde:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Vervang de standaardcode van de OnGetCallbackAsync
methode door de volgende code. De code doorloopt de claimwoordenlijst. Claims worden toegevoegd (ingevuld) of bijgewerkt voor elke gebruiker. Wanneer claims worden toegevoegd of bijgewerkt, wordt de aanmelding van de gebruiker vernieuwd met behulp van de SignInManager<TUser>, waarbij de bestaande verificatie-eigenschappen (AuthenticationProperties
) behouden blijven.
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();
}
}
Er wordt een vergelijkbare benadering uitgevoerd wanneer claims worden gewijzigd terwijl een gebruiker is aangemeld, maar een backfill-stap niet is vereist. Als u de claims van een gebruiker wilt bijwerken, roept u het volgende aan op de gebruiker:
- UserManager.ReplaceClaimAsync op de gebruiker voor claims die zijn opgeslagen in de identiteitsdatabase.
- SignInManager.RefreshSignInAsync op de gebruiker om het genereren van een nieuwe verificatie af te dwingen cookie.
Claimacties en claims verwijderen
ClaimActionCollection.Remove(String) verwijdert alle claimacties voor de opgegeven ClaimType uit de verzameling. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) verwijdert een claim van de opgegeven ClaimType uit de identiteit. DeleteClaim wordt voornamelijk gebruikt met OpenID Connect (OIDC) om door protocol gegenereerde claims te verwijderen.
Voorbeeld-app-uitvoer
Voer de voorbeeld-app uit en selecteer de koppeling 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
Aanvraaggegevens doorsturen met een proxy of load balancer
Als de app wordt geïmplementeerd achter een proxyserver of load balancer, worden sommige van de oorspronkelijke aanvraaggegevens mogelijk doorgestuurd naar de app in aanvraagheaders. Deze informatie omvat meestal het beveiligde aanvraagschema (https
), host- en client-IP-adres. Apps lezen deze aanvraagheaders niet automatisch om de oorspronkelijke aanvraaggegevens te detecteren en te gebruiken.
Het schema wordt gebruikt bij het genereren van koppelingen die van invloed is op de verificatiestroom met externe providers. Het verlies van het beveiligde schema (https
) resulteert in het genereren van onjuiste, onveilige omleidings-URL's.
Gebruik Forwarded Headers Middleware om de oorspronkelijke aanvraaggegevens beschikbaar te maken voor de app voor aanvraagverwerking.
Zie ASP.NET Core configureren voor gebruik met proxyservers en load balancersvoor meer informatie.
Een ASP.NET Core-app kan aanvullende claims en tokens van externe verificatieproviders tot stand brengen, zoals Facebook, Google, Microsoft en Twitter. Elke provider toont verschillende informatie over gebruikers op het platform, maar het patroon voor het ontvangen en transformeren van gebruikersgegevens in aanvullende claims is hetzelfde.
voorbeeldcode weergeven of downloaden (hoe te downloaden)
Voorwaarden
Bepaal welke externe verificatieproviders in de app moeten worden ondersteund. Registreer de app voor elke provider en haal een client-id en clientgeheim op. Zie Facebook- en Google-verificatie in ASP.NET Corevoor meer informatie. De voorbeeld-app maakt gebruik van de Google-verificatieprovider.
De client-id en het clientgeheim instellen
De OAuth-verificatieprovider brengt een vertrouwensrelatie tot stand met een app met behulp van een client-id en clientgeheim. Client-id- en clientgeheimwaarden worden gemaakt voor de app door de externe verificatieprovider wanneer de app is geregistreerd bij de provider. Elke externe provider die door de app wordt gebruikt, moet onafhankelijk worden geconfigureerd met de client-id en het clientgeheim van de provider. Zie de onderwerpen van de externe verificatieprovider die van toepassing zijn op uw scenario voor meer informatie:
- Facebook-verificatie
- Google-verificatie
- Microsoft-verificatie
- Twitter-verificatie
- Andere authenticatieproviders
- OpenIdConnect
Optionele claims die worden verzonden in de ID of het toegangstoken van de authenticatieprovider, worden meestal geconfigureerd in de online portal van de provider. Met Microsoft Entra ID kunt u bijvoorbeeld optieclaims toewijzen aan het ID-token van de app in de tokenconfiguratie van de app-registratie blade. Zie Procedure: Optionele claims opgeven voor uw app (Azure-documentatie)voor meer informatie. Raadpleeg voor andere providers hun externe documentatiesets.
De voorbeeld-app configureert de Google-verificatieprovider met een client-id en clientgeheim dat wordt geleverd door 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;
};
});
Het verificatiebereik instellen
Geef de lijst met machtigingen op die moeten worden opgehaald van de provider door de Scopeop te geven. Verificatiebereiken voor algemene externe providers worden weergegeven in de volgende tabel.
Aanbieder | Draagwijdte |
---|---|
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 |
In de voorbeeld-app worden de Google-machtigingen profile
, email
en openid
automatisch door het framework toegevoegd wanneer AddGoogle wordt aangeroepen op de AuthenticationBuilder. Als de app extra toegangsrechten vereist, voegt u deze toe aan de opties. In het volgende voorbeeld wordt de Google https://www.googleapis.com/auth/user.birthday.read
scope toegevoegd om de verjaardag van de gebruiker op te halen.
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Gebruikersgegevenssleutels toewijzen en claims maken
Geef in de opties van de provider een MapJsonKey of MapJsonSubKey op voor elke sleutel/subsleutel in de JSON-gebruikersgegevens van de externe provider om de app-identiteit te lezen bij aanmelding. Zie ClaimTypesvoor meer informatie over claimtypen.
De voorbeeld-app maakt landinstellingen (urn:google:locale
) en afbeeldingsclaims (urn:google:picture
) op basis van de locale
- en picture
-sleutels in Google-gebruikersgegevens:
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
wordt met SignInAsynceen IdentityUser (ApplicationUser
) aangemeld bij de app. Tijdens het aanmeldingsproces kan de UserManager<TUser> een ApplicationUser
claim opslaan voor gebruikersgegevens die beschikbaar zijn via de Principal.
In de voorbeeld-app stelt OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) de locale-instelling (urn:google:locale
) en afbeeldingsclaims (urn:google:picture
) vast voor de aangemelde ApplicationUser
, inclusief een claim voor 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();
}
De claims van een gebruiker worden standaard opgeslagen in de verificatie cookie. Als de verificatie cookie te groot is, kan dit ertoe leiden dat de app mislukt omdat:
- De browser detecteert dat de cookie header te lang is.
- De totale grootte van de aanvraag is te groot.
Als er een grote hoeveelheid gebruikersgegevens is vereist voor het verwerken van gebruikersaanvragen:
- Beperk het aantal en de grootte van gebruikersclaims voor aanvraagverwerking tot alleen wat de app nodig heeft.
- Gebruik een aangepaste ITicketStore voor de SessionStore van de Cookie Authentication Middleware om identiteit op te slaan in aanvragen. Bewaar grote hoeveelheden identiteitsgegevens op de server terwijl alleen een kleine sessie-id-sleutel naar de client wordt verzonden.
Het toegangstoken opslaan
SaveTokens bepaalt of toegangs- en vernieuwingstokens na een geslaagde autorisatie moeten worden opgeslagen in de AuthenticationProperties.
SaveTokens
is standaard ingesteld op false
om de grootte van de uiteindelijke verificatie cookiete verkleinen.
Met de voorbeeld-app wordt de waarde van SaveTokens
ingesteld op 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;
};
});
Wanneer OnPostConfirmationAsync
wordt uitgevoerd, slaat het systeem het toegangstoken (ExternalLoginInfo.AuthenticationTokens) van de externe provider op in de ApplicationUser
van de AuthenticationProperties
.
De voorbeeld-app slaat het toegangstoken op in OnPostConfirmationAsync
(nieuwe gebruikersregistratie) en OnGetCallbackAsync
(eerder geregistreerde gebruiker) 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();
}
Notitie
Zie ASP.NET Core-server en Blazor Web App aanvullende beveiligingsscenario'svoor informatie over het doorgeven van tokens aan de Razor onderdelen van een Blazor-app aan de serverzijde.
Aanvullende aangepaste tokens toevoegen
Om te demonstreren hoe u een aangepast token toevoegt, dat wordt opgeslagen als onderdeel van SaveTokens
, voegt de voorbeeldapp een AuthenticationToken toe met de huidige DateTime voor een AuthenticationToken.Name van 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;
};
});
Claims maken en toevoegen
Het framework biedt algemene acties en uitbreidingsmethoden voor het maken en toevoegen van claims aan de verzameling. Zie de ClaimActionCollectionMapExtensions en ClaimActionCollectionUniqueExtensionsvoor meer informatie.
Gebruikers kunnen aangepaste acties definiëren door af te leiden van ClaimAction en de abstracte Run methode te implementeren.
Zie Microsoft.AspNetCore.Authentication.OAuth.Claimsvoor meer informatie.
Gebruikersclaims toevoegen en bijwerken
Claims worden gekopieerd van externe providers naar de gebruikersdatabase bij de eerste registratie, niet bij inloggen. Als extra claims zijn ingeschakeld in een app nadat een gebruiker zich heeft geregistreerd om de app te gebruiken, roept u SignInManager.RefreshSignInAsync- aan op een gebruiker om het genereren van een nieuwe verificatie af te dwingen cookie.
In de ontwikkelomgeving die met testgebruikersaccounts werkt, kunt u gewoon het gebruikersaccount verwijderen en opnieuw maken. Voor productiesystemen kunnen nieuwe claims die aan de app worden toegevoegd, worden ingevuld in gebruikersaccounts. Nadat de ExternalLogin
pagina in de app op Areas/Pages/Identity/Account/Manage
hebt geplaatst, voegt u de volgende code toe aan de ExternalLoginModel
in het ExternalLogin.cshtml.cs
-bestand.
Voeg een woordenlijst met toegevoegde claims toe. Gebruik de woordenlijstsleutels om de claimtypen op te slaan en gebruik de waarden om een standaardwaarde op te slaan. Voeg de volgende regel toe aan het begin van de klasse. In het volgende voorbeeld wordt ervan uitgegaan dat er één claim wordt toegevoegd voor de Google-afbeelding van de gebruiker met een algemene headshotafbeelding als de standaardwaarde:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Vervang de standaardcode van de OnGetCallbackAsync
methode door de volgende code. De code doorloopt de claimwoordenlijst. Claims worden toegevoegd (aangevuld) of bijgewerkt voor iedere gebruiker. Wanneer claims worden toegevoegd of bijgewerkt, wordt de aanmelding van de gebruiker vernieuwd met behulp van de SignInManager<TUser>, waarbij de bestaande verificatie-eigenschappen (AuthenticationProperties
) behouden blijven.
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();
}
}
Er wordt een vergelijkbare benadering uitgevoerd wanneer claims worden gewijzigd terwijl een gebruiker is aangemeld, maar een backfill-stap niet is vereist. Als u de claims van een gebruiker wilt bijwerken, roept u het volgende aan op de gebruiker:
- UserManager.ReplaceClaimAsync op de gebruiker voor claims die zijn opgeslagen in de identiteitsdatabase.
- SignInManager.RefreshSignInAsync op de gebruiker om het genereren van een nieuwe verificatie af te dwingen cookie.
Verwijdering van claimacties en claims
ClaimActionCollection.Remove(String) verwijdert alle claimacties voor de opgegeven ClaimType uit de verzameling. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) een claim verwijdert van de opgegeven ClaimType uit de identiteit. DeleteClaim wordt voornamelijk gebruikt met OpenID Connect (OIDC) om door protocol gegenereerde claims te verwijderen.
Voorbeeld-app-uitvoer
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
Verzoekinformatie doorsturen met een proxy of load balancer
Als de app wordt geïmplementeerd achter een proxyserver of load balancer, worden sommige van de oorspronkelijke aanvraaggegevens mogelijk doorgestuurd naar de app in aanvraagheaders. Deze informatie omvat meestal het beveiligde aanvraagschema (https
), host- en client-IP-adres. Apps lezen deze aanvraagheaders niet automatisch om de oorspronkelijke aanvraaggegevens te detecteren en te gebruiken.
Het schema wordt gebruikt bij het genereren van koppelingen die van invloed is op de verificatiestroom met externe providers. Het verlies van het beveiligde schema (https
) resulteert in het genereren van onjuiste, onveilige omleidings-URL's.
Gebruik Forwarded Headers Middleware om de oorspronkelijke aanvraaggegevens beschikbaar te maken voor de app voor aanvraagverwerking.
Zie ASP.NET Core configureren voor gebruik met proxyservers en load balancersvoor meer informatie.
Een ASP.NET Core-app kan aanvullende claims en tokens van externe verificatieproviders tot stand brengen, zoals Facebook, Google, Microsoft en Twitter. Elke provider toont verschillende informatie over gebruikers op het platform, maar het patroon voor het ontvangen en transformeren van gebruikersgegevens in aanvullende claims is hetzelfde.
Voorbeeldcode bekijken of downloaden (hoe te downloaden)
Voorwaarden
Bepaal welke externe verificatieproviders in de app moeten worden ondersteund. Registreer de app voor elke provider en haal een client-id en clientgeheim op. Zie Facebook- en Google-verificatie in ASP.NET Corevoor meer informatie. De voorbeeld-app maakt gebruik van de Google-verificatieprovider.
De client-id en het clientgeheim instellen
De OAuth-verificatieprovider brengt een vertrouwensrelatie tot stand met een app met behulp van een client-id en clientgeheim. Client-id- en clientgeheimwaarden worden gemaakt voor de app door de externe verificatieprovider wanneer de app is geregistreerd bij de provider. Elke externe provider die door de app wordt gebruikt, moet onafhankelijk worden geconfigureerd met de client-id en het clientgeheim van de provider. Zie de onderwerpen van de externe verificatieprovider die van toepassing zijn op uw scenario voor meer informatie:
- Facebook-verificatie
- Google-verificatie
- Microsoft-verificatie
- Twitter-verificatie
- andere authenticatieproviders
- OpenIdConnect
De voorbeeld-app configureert de Google-verificatieprovider met een client-id en clientgeheim dat wordt geleverd door 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;
};
});
Het verificatiebereik instellen
Geef de lijst met machtigingen op die moeten worden opgehaald van de provider door de Scopeop te geven. Verificatiebereiken voor algemene externe providers worden weergegeven in de volgende tabel.
Aanbieder | Draagwijdte |
---|---|
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 |
In de voorbeeldapp voegt het framework automatisch het userinfo.profile
-bereik van Google toe wanneer AddGoogle wordt aangeroepen op de AuthenticationBuilder. Als de app extra machtigingen vereist, voeg deze dan toe aan de opties. In het volgende voorbeeld wordt het bereik van Google https://www.googleapis.com/auth/user.birthday.read
toegevoegd om de verjaardag van een gebruiker op te halen.
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Gebruikersgegevenssleutels toewijzen en claims maken
Geef in de opties van de provider een MapJsonKey of MapJsonSubKey op voor elke sleutel/subsleutel in de JSON-gebruikersgegevens van de externe provider om de app-identiteit te lezen bij aanmelding. Zie ClaimTypesvoor meer informatie over claimtypen.
De voorbeeld-app maakt landinstellingen (urn:google:locale
) en afbeeldingsclaims (urn:google:picture
) op basis van de locale
- en picture
-sleutels in Google-gebruikersgegevens:
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
wordt met SignInAsynceen IdentityUser (ApplicationUser
) aangemeld bij de app. Tijdens het aanmeldingsproces kan de UserManager<TUser> een ApplicationUser
claims opslaan voor gebruikersgegevens die beschikbaar zijn via de Principal.
In de voorbeeld-app stelt OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) de taalinstelling (urn:google:locale
) en afbeeldingsclaim (urn:google:picture
) vast voor de ingelogde ApplicationUser
, inclusief een claim voor 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();
}
De claims van een gebruiker worden standaard opgeslagen in de verificatie cookie. Als de verificatie cookie te groot is, kan dit ertoe leiden dat de app mislukt omdat:
- De browser detecteert dat de cookie header te lang is.
- De totale grootte van de aanvraag is te groot.
Als er een grote hoeveelheid gebruikersgegevens is vereist voor het verwerken van gebruikersaanvragen:
- Beperk het aantal en de grootte van gebruikersclaims voor aanvraagverwerking tot alleen wat de app nodig heeft.
- Gebruik een aangepaste ITicketStore voor de SessionStore van de Cookie Authentication Middleware om identiteit op te slaan in aanvragen. Bewaar grote hoeveelheden identiteitsgegevens op de server terwijl alleen een kleine sessie-id-sleutel naar de client wordt verzonden.
Het toegangstoken opslaan
SaveTokens bepaalt of toegangs- en vernieuwingstokens na een geslaagde autorisatie moeten worden opgeslagen in de AuthenticationProperties.
SaveTokens
is standaard ingesteld op false
om de grootte van de uiteindelijke verificatie cookiete verkleinen.
Met de voorbeeld-app wordt de waarde van SaveTokens
ingesteld op 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;
};
});
Wanneer OnPostConfirmationAsync
is uitgevoerd, wordt het toegangstoken (ExternalLoginInfo.AuthenticationTokens) van de externe provider opgeslagen in de ApplicationUser
AuthenticationProperties
.
De voorbeeld-app slaat het toegangstoken op in OnPostConfirmationAsync
(nieuwe gebruikersregistratie) en OnGetCallbackAsync
(eerder geregistreerde gebruiker) 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();
}
Aanvullende aangepaste tokens toevoegen
Om te laten zien hoe u een aangepast token toevoegt, dat als onderdeel van SaveTokens
is opgeslagen, voegt de voorbeeld-app een AuthenticationToken toe met de huidige DateTime voor een AuthenticationToken.Name van 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;
};
});
Claims maken en toevoegen
Het framework biedt algemene acties en uitbreidingsmethoden voor het maken en toevoegen van claims aan de verzameling. Zie de ClaimActionCollectionMapExtensions en ClaimActionCollectionUniqueExtensionsvoor meer informatie.
Gebruikers kunnen aangepaste acties definiëren door af te leiden van ClaimAction en de abstracte Run methode te implementeren.
Zie Microsoft.AspNetCore.Authentication.OAuth.Claimsvoor meer informatie.
Verwijdering van claimacties en claims
ClaimActionCollection.Remove(String) verwijdert alle claimacties voor de opgegeven ClaimType uit de verzameling. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) verwijdert een claim van de opgegeven ClaimType uit de identiteit. DeleteClaim wordt voornamelijk gebruikt met OpenID Connect (OIDC) om door protocol gegenereerde claims te verwijderen.
Voorbeeld-app-uitvoer
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
Verzoekinformatie doorsturen met een proxy of load balancer
Als de app wordt geïmplementeerd achter een proxyserver of load balancer, worden sommige van de oorspronkelijke aanvraaggegevens mogelijk doorgestuurd naar de app in aanvraagheaders. Deze informatie omvat meestal het beveiligde aanvraagschema (https
), host- en client-IP-adres. Apps lezen deze aanvraagheaders niet automatisch om de oorspronkelijke aanvraaggegevens te detecteren en te gebruiken.
Het schema wordt gebruikt bij het genereren van koppelingen die van invloed is op de verificatiestroom met externe providers. Het verlies van het beveiligde schema (https
) resulteert in het genereren van onjuiste, onveilige omleidings-URL's.
Gebruik Forwarded Headers Middleware om de oorspronkelijke aanvraaggegevens beschikbaar te maken voor de app voor aanvraagverwerking.
Zie ASP.NET Core configureren voor gebruik met proxyservers en load balancersvoor meer informatie.
Aanvullende informatiebronnen
-
dotnet/AspNetCore Engineering SocialSample-app: de gekoppelde voorbeeld-app bevindt zich in de dotnet-/AspNetCore GitHub-opslagplaats van de
main
engineering-vertakking. Demain
vertakking bevat code onder actieve ontwikkeling voor de volgende release van ASP.NET Core. Als u een versie van de voorbeeld-app wilt zien voor een uitgebrachte versie van ASP.NET Core, gebruikt u de vervolgkeuzelijst Branch om een releasevertakking te selecteren (bijvoorbeeldrelease/{X.Y}
).