Mempertahankan klaim dan token tambahan dari penyedia eksternal di ASP.NET Core
Aplikasi ASP.NET Core dapat membuat klaim dan token tambahan dari penyedia autentikasi eksternal, seperti Facebook, Google, Microsoft, dan Twitter. Setiap penyedia mengungkapkan informasi yang berbeda tentang pengguna di platformnya, tetapi pola untuk menerima dan mengubah data pengguna menjadi klaim tambahan sama.
Prasyarat
Tentukan penyedia autentikasi eksternal mana yang akan didukung di aplikasi. Untuk setiap penyedia, daftarkan aplikasi dan dapatkan ID klien dan rahasia klien. Untuk informasi selengkapnya, lihat Autentikasi Facebook dan Google di ASP.NET Core. Aplikasi sampel menggunakan penyedia autentikasi Google.
Mengatur ID klien dan rahasia klien
Penyedia autentikasi OAuth membuat hubungan kepercayaan dengan aplikasi menggunakan ID klien dan rahasia klien. ID Klien dan nilai rahasia klien dibuat untuk aplikasi oleh penyedia autentikasi eksternal saat aplikasi terdaftar di penyedia. Setiap penyedia eksternal yang digunakan aplikasi harus dikonfigurasi secara independen dengan ID klien penyedia dan rahasia klien. Untuk informasi selengkapnya, lihat topik penyedia autentikasi eksternal yang berlaku:
- Autentikasi Facebook
- Autentikasi Google
- Autentikasi Microsoft
- Autentikasi Twitter
- Penyedia autentikasi lainnya
- OpenIdConnect
Klaim opsional yang dikirim dalam ID atau token akses dari penyedia autentikasi biasanya dikonfigurasi di portal online penyedia. Misalnya, MICROSOFT Entra ID mengizinkan penetapan klaim opsional ke token ID aplikasi di bilah konfigurasi Token pendaftaran aplikasi. Untuk informasi selengkapnya, lihat Cara: Memberikan klaim opsional ke aplikasi Anda (dokumentasi Azure). Untuk penyedia lain, lihat kumpulan dokumentasi eksternal mereka.
Aplikasi sampel mengonfigurasi penyedia autentikasi Google dengan ID klien dan rahasia klien yang disediakan oleh 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.
Menetapkan cakupan autentikasi
Tentukan daftar izin yang akan diambil dari penyedia dengan menentukan Scope. Cakupan autentikasi untuk penyedia eksternal umum muncul dalam tabel berikut.
Penyedia | Cakupan |
---|---|
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 |
Dalam aplikasi sampel, cakupan Google profile
, email
, dan openid
secara otomatis ditambahkan oleh kerangka kerja saat AddGoogle dipanggil di AuthenticationBuilder. Jika aplikasi memerlukan cakupan tambahan, tambahkan ke opsi . Dalam contoh berikut, cakupan Google https://www.googleapis.com/auth/user.birthday.read
ditambahkan untuk mengambil ulang tahun pengguna:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Memetakan kunci data pengguna dan membuat klaim
Dalam opsi penyedia, tentukan MapJsonKey atau MapJsonSubKey untuk setiap kunci atau subkunci dalam data pengguna JSON penyedia eksternal agar aplikasi identity dapat dibaca saat masuk. Untuk informasi selengkapnya tentang jenis klaim, lihat ClaimTypes.
Aplikasi sampel membuat klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) dari locale
kunci dan picture
di data pengguna 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;
};
});
Di Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, IdentityUser (ApplicationUser
) masuk ke aplikasi dengan SignInAsync. Selama proses masuk, UserManager<TUser> dapat menyimpan klaim untuk data pengguna yang ApplicationUser
Principaltersedia dari .
Di aplikasi sampel, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) menetapkan klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) untuk yang masuk ApplicationUser
, termasuk klaim untuk 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();
}
Secara default, klaim pengguna disimpan dalam autentikasi cookie. Jika autentikasi terlalu besar, autentikasi cookie dapat menyebabkan aplikasi gagal karena:
- Browser mendeteksi bahwa cookie header terlalu panjang.
- Ukuran keseluruhan permintaan terlalu besar.
Jika sejumlah besar data pengguna diperlukan untuk memproses permintaan pengguna:
- Batasi jumlah dan ukuran klaim pengguna untuk pemrosesan permintaan hanya untuk apa yang dibutuhkan aplikasi.
- Gunakan kustom ITicketStore untuk Cookie Middleware SessionStore Autentikasi untuk menyimpan identity di seluruh permintaan. Pertahankan sejumlah besar identity informasi di server sambil hanya mengirim kunci pengidentifikasi sesi kecil ke klien.
Menyimpan token akses
SaveTokens menentukan apakah token akses dan refresh harus disimpan setelah AuthenticationProperties otorisasi berhasil. SaveTokens
diatur ke false
secara default untuk mengurangi ukuran autentikasi cookieakhir .
Aplikasi sampel menetapkan nilai SaveTokens
menjadi true
di 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;
};
});
Saat OnPostConfirmationAsync
dijalankan, simpan token akses (ExternalLoginInfo.AuthenticationTokens) dari penyedia eksternal di ApplicationUser
.AuthenticationProperties
Aplikasi sampel menyimpan token akses di OnPostConfirmationAsync
(pendaftaran pengguna baru) dan OnGetCallbackAsync
(pengguna yang terdaftar sebelumnya) di 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();
}
Catatan
Untuk informasi tentang meneruskan token ke Razor komponen aplikasi sisi Blazor server, lihat Skenario keamanan tambahan sisi server ASP.NET CoreBlazor.
Cara menambahkan token kustom tambahan
Untuk menunjukkan cara menambahkan token kustom, yang disimpan sebagai bagian SaveTokens
dari , aplikasi sampel menambahkan AuthenticationToken dengan saat ini DateTime untuk AuthenticationToken.Name dari 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;
};
});
Membuat dan menambahkan klaim
Kerangka kerja menyediakan tindakan umum dan metode ekstensi untuk membuat dan menambahkan klaim ke koleksi. Untuk informasi selengkapnya, lihat ClaimActionCollectionMapExtensions dan ClaimActionCollectionUniqueExtensions.
Pengguna dapat menentukan tindakan kustom dengan berasal dari ClaimAction dan menerapkan metode abstrak Run .
Untuk informasi selengkapnya, lihat Microsoft.AspNetCore.Authentication.OAuth.Claims .
Menambahkan dan memperbarui klaim pengguna
Klaim disalin dari penyedia eksternal ke database pengguna pada pendaftaran pertama, bukan saat masuk. Jika klaim tambahan diaktifkan di aplikasi setelah pengguna mendaftar untuk menggunakan aplikasi, panggil SignInManager.RefreshSignInAsync pada pengguna untuk memaksa pembuatan autentikasi cookiebaru .
Di lingkungan Pengembangan yang bekerja dengan akun pengguna uji, hapus dan buat ulang akun pengguna. Untuk sistem produksi, klaim baru yang ditambahkan ke aplikasi dapat diisi ulang ke akun pengguna. Setelah membuat perancah ExternalLogin
halaman ke dalam aplikasi di Areas/Pages/Identity/Account/Manage
, tambahkan kode berikut ke ExternalLoginModel
dalam ExternalLogin.cshtml.cs
file.
Tambahkan kamus klaim tambahan. Gunakan kunci kamus untuk menahan jenis klaim, dan gunakan nilai untuk menyimpan nilai default. Tambahkan baris berikut ke bagian atas kelas. Contoh berikut mengasumsikan bahwa satu klaim ditambahkan untuk gambar Google pengguna dengan gambar headshot generik sebagai nilai default:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Ganti kode OnGetCallbackAsync
default metode dengan kode berikut. Kode mengulang melalui kamus klaim. Klaim ditambahkan (diisi ulang) atau diperbarui untuk setiap pengguna. Saat klaim ditambahkan atau diperbarui, rincian masuk pengguna di-refresh menggunakan SignInManager<TUser>, mempertahankan properti autentikasi yang ada (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();
}
}
Pendekatan serupa diambil ketika klaim berubah saat pengguna masuk tetapi langkah isi ulang tidak diperlukan. Untuk memperbarui klaim pengguna, panggil yang berikut ini pada pengguna:
- UserManager.ReplaceClaimAsync pada pengguna untuk klaim yang disimpan dalam identity database.
- SignInManager.RefreshSignInAsync pada pengguna untuk memaksa pembuatan autentikasi cookiebaru .
Menghapus tindakan dan klaim klaim
ClaimActionCollection.Remove(String) menghapus semua tindakan klaim untuk yang diberikan ClaimType dari koleksi. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) menghapus klaim yang diberikan ClaimType dari identity. DeleteClaim terutama digunakan dengan OpenID Connect (OIDC) untuk menghapus klaim yang dihasilkan protokol.
Contoh output aplikasi
Jalankan aplikasi sampel dan pilih tautan 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
Meneruskan informasi permintaan dengan proksi atau penyeimbang beban
Jika aplikasi disebarkan di belakang server proksi atau penyeimbang beban, beberapa informasi permintaan asli mungkin diteruskan ke aplikasi di header permintaan. Informasi ini biasanya mencakup skema permintaan aman (https
), host, dan alamat IP klien. Aplikasi tidak secara otomatis membaca header permintaan ini untuk menemukan dan menggunakan informasi permintaan asli.
Skema ini digunakan dalam pembuatan tautan yang memengaruhi alur autentikasi dengan penyedia eksternal. Kehilangan skema aman (https
) akan menyebabkan aplikasi menghasilkan URL pengalihan tidak aman yang salah.
Pakai Forwarded Headers Middleware guna menyediakan informasi permintaan asli untuk aplikasi untuk pemrosesan permintaan.
Untuk informasi selengkapnya, lihat Mengonfigurasi ASP.NET Core untuk bekerja dengan server proxy dan memuat penyeimbang.
Aplikasi ASP.NET Core dapat membuat klaim dan token tambahan dari penyedia autentikasi eksternal, seperti Facebook, Google, Microsoft, dan Twitter. Setiap penyedia mengungkapkan informasi yang berbeda tentang pengguna di platformnya, tetapi pola untuk menerima dan mengubah data pengguna menjadi klaim tambahan sama.
Melihat atau mengunduh kode sampel (cara mengunduh)
Prasyarat
Tentukan penyedia autentikasi eksternal mana yang akan didukung di aplikasi. Untuk setiap penyedia, daftarkan aplikasi dan dapatkan ID klien dan rahasia klien. Untuk informasi selengkapnya, lihat Autentikasi Facebook dan Google di ASP.NET Core. Aplikasi sampel menggunakan penyedia autentikasi Google.
Mengatur ID klien dan rahasia klien
Penyedia autentikasi OAuth membuat hubungan kepercayaan dengan aplikasi menggunakan ID klien dan rahasia klien. ID Klien dan nilai rahasia klien dibuat untuk aplikasi oleh penyedia autentikasi eksternal saat aplikasi terdaftar di penyedia. Setiap penyedia eksternal yang digunakan aplikasi harus dikonfigurasi secara independen dengan ID klien penyedia dan rahasia klien. Untuk informasi selengkapnya, lihat topik penyedia autentikasi eksternal yang berlaku untuk skenario Anda:
- Autentikasi Facebook
- Autentikasi Google
- Autentikasi Microsoft
- Autentikasi Twitter
- Penyedia autentikasi lainnya
- OpenIdConnect
Klaim opsional yang dikirim dalam ID atau token akses dari penyedia autentikasi biasanya dikonfigurasi di portal online penyedia. Misalnya, MICROSOFT Entra ID memungkinkan Anda menetapkan klaim opsional ke token ID aplikasi di bilah konfigurasi Token pendaftaran aplikasi. Untuk informasi selengkapnya, lihat Cara: Memberikan klaim opsional ke aplikasi Anda (dokumentasi Azure). Untuk penyedia lain, lihat kumpulan dokumentasi eksternal mereka.
Aplikasi sampel mengonfigurasi penyedia autentikasi Google dengan ID klien dan rahasia klien yang disediakan oleh 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;
};
});
Menetapkan cakupan autentikasi
Tentukan daftar izin yang akan diambil dari penyedia dengan menentukan Scope. Cakupan autentikasi untuk penyedia eksternal umum muncul dalam tabel berikut.
Penyedia | Cakupan |
---|---|
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 |
Dalam aplikasi sampel, cakupan Google profile
, email
, dan openid
secara otomatis ditambahkan oleh kerangka kerja saat AddGoogle dipanggil di AuthenticationBuilder. Jika aplikasi memerlukan cakupan tambahan, tambahkan ke opsi . Dalam contoh berikut, cakupan Google https://www.googleapis.com/auth/user.birthday.read
ditambahkan untuk mengambil ulang tahun pengguna:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Memetakan kunci data pengguna dan membuat klaim
Dalam opsi penyedia, tentukan MapJsonKey atau MapJsonSubKey untuk setiap kunci/subkunci dalam data pengguna JSON penyedia eksternal agar aplikasi identity dapat dibaca saat masuk. Untuk informasi selengkapnya tentang jenis klaim, lihat ClaimTypes.
Aplikasi sampel membuat klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) dari locale
kunci dan picture
di data pengguna 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;
};
});
Di Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, IdentityUser (ApplicationUser
) masuk ke aplikasi dengan SignInAsync. Selama proses masuk, UserManager<TUser> dapat menyimpan klaim untuk data pengguna yang ApplicationUser
Principaltersedia dari .
Di aplikasi sampel, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) menetapkan klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) untuk yang masuk ApplicationUser
, termasuk klaim untuk 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();
}
Secara default, klaim pengguna disimpan dalam autentikasi cookie. Jika autentikasi terlalu besar, autentikasi cookie dapat menyebabkan aplikasi gagal karena:
- Browser mendeteksi bahwa cookie header terlalu panjang.
- Ukuran keseluruhan permintaan terlalu besar.
Jika sejumlah besar data pengguna diperlukan untuk memproses permintaan pengguna:
- Batasi jumlah dan ukuran klaim pengguna untuk pemrosesan permintaan hanya untuk apa yang dibutuhkan aplikasi.
- Gunakan kustom ITicketStore untuk Cookie Middleware SessionStore Autentikasi untuk menyimpan identity di seluruh permintaan. Pertahankan sejumlah besar identity informasi di server sambil hanya mengirim kunci pengidentifikasi sesi kecil ke klien.
Menyimpan token akses
SaveTokens menentukan apakah token akses dan refresh harus disimpan setelah AuthenticationProperties otorisasi berhasil. SaveTokens
diatur ke false
secara default untuk mengurangi ukuran autentikasi cookieakhir .
Aplikasi sampel menetapkan nilai SaveTokens
menjadi true
di 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;
};
});
Saat OnPostConfirmationAsync
dijalankan, simpan token akses (ExternalLoginInfo.AuthenticationTokens) dari penyedia eksternal di ApplicationUser
.AuthenticationProperties
Aplikasi sampel menyimpan token akses di OnPostConfirmationAsync
(pendaftaran pengguna baru) dan OnGetCallbackAsync
(pengguna yang terdaftar sebelumnya) di 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();
}
Catatan
Untuk informasi tentang meneruskan token ke Razor komponen aplikasi sisi Blazor server, lihat Skenario keamanan tambahan sisi server ASP.NET CoreBlazor.
Cara menambahkan token kustom tambahan
Untuk menunjukkan cara menambahkan token kustom, yang disimpan sebagai bagian SaveTokens
dari , aplikasi sampel menambahkan AuthenticationToken dengan saat ini DateTime untuk AuthenticationToken.Name dari 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;
};
});
Membuat dan menambahkan klaim
Kerangka kerja menyediakan tindakan umum dan metode ekstensi untuk membuat dan menambahkan klaim ke koleksi. Untuk informasi selengkapnya, lihat ClaimActionCollectionMapExtensions dan ClaimActionCollectionUniqueExtensions.
Pengguna dapat menentukan tindakan kustom dengan berasal dari ClaimAction dan menerapkan metode abstrak Run .
Untuk informasi selengkapnya, lihat Microsoft.AspNetCore.Authentication.OAuth.Claims .
Menambahkan dan memperbarui klaim pengguna
Klaim disalin dari penyedia eksternal ke database pengguna pada pendaftaran pertama, bukan saat masuk. Jika klaim tambahan diaktifkan di aplikasi setelah pengguna mendaftar untuk menggunakan aplikasi, panggil SignInManager.RefreshSignInAsync pada pengguna untuk memaksa pembuatan autentikasi cookiebaru .
Di lingkungan Pengembangan yang bekerja dengan akun pengguna uji, Anda cukup menghapus dan membuat ulang akun pengguna. Untuk sistem produksi, klaim baru yang ditambahkan ke aplikasi dapat diisi ulang ke akun pengguna. Setelah membuat perancah ExternalLogin
halaman ke dalam aplikasi di Areas/Pages/Identity/Account/Manage
, tambahkan kode berikut ke ExternalLoginModel
dalam ExternalLogin.cshtml.cs
file.
Tambahkan kamus klaim tambahan. Gunakan kunci kamus untuk menahan jenis klaim, dan gunakan nilai untuk menyimpan nilai default. Tambahkan baris berikut ke bagian atas kelas. Contoh berikut mengasumsikan bahwa satu klaim ditambahkan untuk gambar Google pengguna dengan gambar headshot generik sebagai nilai default:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Ganti kode OnGetCallbackAsync
default metode dengan kode berikut. Kode mengulang melalui kamus klaim. Klaim ditambahkan (diisi ulang) atau diperbarui untuk setiap pengguna. Saat klaim ditambahkan atau diperbarui, rincian masuk pengguna di-refresh menggunakan SignInManager<TUser>, mempertahankan properti autentikasi yang ada (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();
}
}
Pendekatan serupa diambil ketika klaim berubah saat pengguna masuk tetapi langkah isi ulang tidak diperlukan. Untuk memperbarui klaim pengguna, panggil yang berikut ini pada pengguna:
- UserManager.ReplaceClaimAsync pada pengguna untuk klaim yang disimpan dalam identity database.
- SignInManager.RefreshSignInAsync pada pengguna untuk memaksa pembuatan autentikasi cookiebaru .
Penghapusan tindakan dan klaim klaim
ClaimActionCollection.Remove(String) menghapus semua tindakan klaim untuk yang diberikan ClaimType dari koleksi. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) menghapus klaim yang diberikan ClaimType dari identity. DeleteClaim terutama digunakan dengan OpenID Connect (OIDC) untuk menghapus klaim yang dihasilkan protokol.
Contoh output aplikasi
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
Meneruskan informasi permintaan dengan proksi atau penyeimbang beban
Jika aplikasi disebarkan di belakang server proksi atau penyeimbang beban, beberapa informasi permintaan asli mungkin diteruskan ke aplikasi di header permintaan. Informasi ini biasanya mencakup skema permintaan aman (https
), host, dan alamat IP klien. Aplikasi tidak secara otomatis membaca header permintaan ini untuk menemukan dan menggunakan informasi permintaan asli.
Skema ini digunakan dalam pembuatan tautan yang memengaruhi alur autentikasi dengan penyedia eksternal. Kehilangan skema aman (https
) akan menyebabkan aplikasi menghasilkan URL pengalihan tidak aman yang salah.
Pakai Forwarded Headers Middleware guna menyediakan informasi permintaan asli untuk aplikasi untuk pemrosesan permintaan.
Untuk informasi selengkapnya, lihat Mengonfigurasi ASP.NET Core untuk bekerja dengan server proxy dan memuat penyeimbang.
Aplikasi ASP.NET Core dapat membuat klaim dan token tambahan dari penyedia autentikasi eksternal, seperti Facebook, Google, Microsoft, dan Twitter. Setiap penyedia mengungkapkan informasi yang berbeda tentang pengguna di platformnya, tetapi pola untuk menerima dan mengubah data pengguna menjadi klaim tambahan sama.
Melihat atau mengunduh kode sampel (cara mengunduh)
Prasyarat
Tentukan penyedia autentikasi eksternal mana yang akan didukung di aplikasi. Untuk setiap penyedia, daftarkan aplikasi dan dapatkan ID klien dan rahasia klien. Untuk informasi selengkapnya, lihat Autentikasi Facebook dan Google di ASP.NET Core. Aplikasi sampel menggunakan penyedia autentikasi Google.
Mengatur ID klien dan rahasia klien
Penyedia autentikasi OAuth membuat hubungan kepercayaan dengan aplikasi menggunakan ID klien dan rahasia klien. ID Klien dan nilai rahasia klien dibuat untuk aplikasi oleh penyedia autentikasi eksternal saat aplikasi terdaftar di penyedia. Setiap penyedia eksternal yang digunakan aplikasi harus dikonfigurasi secara independen dengan ID klien penyedia dan rahasia klien. Untuk informasi selengkapnya, lihat topik penyedia autentikasi eksternal yang berlaku untuk skenario Anda:
- Autentikasi Facebook
- Autentikasi Google
- Autentikasi Microsoft
- Autentikasi Twitter
- Penyedia autentikasi lainnya
- OpenIdConnect
Aplikasi sampel mengonfigurasi penyedia autentikasi Google dengan ID klien dan rahasia klien yang disediakan oleh 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;
};
});
Menetapkan cakupan autentikasi
Tentukan daftar izin yang akan diambil dari penyedia dengan menentukan Scope. Cakupan autentikasi untuk penyedia eksternal umum muncul dalam tabel berikut.
Penyedia | Cakupan |
---|---|
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 |
Di aplikasi sampel, cakupan Google userinfo.profile
secara otomatis ditambahkan oleh kerangka kerja saat AddGoogle dipanggil di AuthenticationBuilder. Jika aplikasi memerlukan cakupan tambahan, tambahkan ke opsi . Dalam contoh berikut, cakupan Google https://www.googleapis.com/auth/user.birthday.read
ditambahkan untuk mengambil ulang tahun pengguna:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Memetakan kunci data pengguna dan membuat klaim
Dalam opsi penyedia, tentukan MapJsonKey atau MapJsonSubKey untuk setiap kunci/subkunci dalam data pengguna JSON penyedia eksternal agar aplikasi identity dapat dibaca saat masuk. Untuk informasi selengkapnya tentang jenis klaim, lihat ClaimTypes.
Aplikasi sampel membuat klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) dari locale
kunci dan picture
di data pengguna 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;
};
});
Di Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, IdentityUser (ApplicationUser
) masuk ke aplikasi dengan SignInAsync. Selama proses masuk, UserManager<TUser> dapat menyimpan klaim untuk data pengguna yang ApplicationUser
Principaltersedia dari .
Di aplikasi sampel, OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) menetapkan klaim lokal (urn:google:locale
) dan gambar (urn:google:picture
) untuk yang masuk ApplicationUser
, termasuk klaim untuk 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();
}
Secara default, klaim pengguna disimpan dalam autentikasi cookie. Jika autentikasi terlalu besar, autentikasi cookie dapat menyebabkan aplikasi gagal karena:
- Browser mendeteksi bahwa cookie header terlalu panjang.
- Ukuran keseluruhan permintaan terlalu besar.
Jika sejumlah besar data pengguna diperlukan untuk memproses permintaan pengguna:
- Batasi jumlah dan ukuran klaim pengguna untuk pemrosesan permintaan hanya untuk apa yang dibutuhkan aplikasi.
- Gunakan kustom ITicketStore untuk Cookie Middleware SessionStore Autentikasi untuk menyimpan identity di seluruh permintaan. Pertahankan sejumlah besar identity informasi di server sambil hanya mengirim kunci pengidentifikasi sesi kecil ke klien.
Menyimpan token akses
SaveTokens menentukan apakah token akses dan refresh harus disimpan setelah AuthenticationProperties otorisasi berhasil. SaveTokens
diatur ke false
secara default untuk mengurangi ukuran autentikasi cookieakhir .
Aplikasi sampel menetapkan nilai SaveTokens
menjadi true
di 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;
};
});
Saat OnPostConfirmationAsync
dijalankan, simpan token akses (ExternalLoginInfo.AuthenticationTokens) dari penyedia eksternal di ApplicationUser
.AuthenticationProperties
Aplikasi sampel menyimpan token akses di OnPostConfirmationAsync
(pendaftaran pengguna baru) dan OnGetCallbackAsync
(pengguna yang terdaftar sebelumnya) di 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();
}
Cara menambahkan token kustom tambahan
Untuk menunjukkan cara menambahkan token kustom, yang disimpan sebagai bagian SaveTokens
dari , aplikasi sampel menambahkan AuthenticationToken dengan saat ini DateTime untuk AuthenticationToken.Name dari 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;
};
});
Membuat dan menambahkan klaim
Kerangka kerja menyediakan tindakan umum dan metode ekstensi untuk membuat dan menambahkan klaim ke koleksi. Untuk informasi selengkapnya, lihat ClaimActionCollectionMapExtensions dan ClaimActionCollectionUniqueExtensions.
Pengguna dapat menentukan tindakan kustom dengan berasal dari ClaimAction dan menerapkan metode abstrak Run .
Untuk informasi selengkapnya, lihat Microsoft.AspNetCore.Authentication.OAuth.Claims .
Penghapusan tindakan dan klaim klaim
ClaimActionCollection.Remove(String) menghapus semua tindakan klaim untuk yang diberikan ClaimType dari koleksi. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) menghapus klaim yang diberikan ClaimType dari identity. DeleteClaim terutama digunakan dengan OpenID Connect (OIDC) untuk menghapus klaim yang dihasilkan protokol.
Contoh output aplikasi
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
Meneruskan informasi permintaan dengan proksi atau penyeimbang beban
Jika aplikasi disebarkan di belakang server proksi atau penyeimbang beban, beberapa informasi permintaan asli mungkin diteruskan ke aplikasi di header permintaan. Informasi ini biasanya mencakup skema permintaan aman (https
), host, dan alamat IP klien. Aplikasi tidak secara otomatis membaca header permintaan ini untuk menemukan dan menggunakan informasi permintaan asli.
Skema ini digunakan dalam pembuatan tautan yang memengaruhi alur autentikasi dengan penyedia eksternal. Kehilangan skema aman (https
) akan menyebabkan aplikasi menghasilkan URL pengalihan tidak aman yang salah.
Pakai Forwarded Headers Middleware guna menyediakan informasi permintaan asli untuk aplikasi untuk pemrosesan permintaan.
Untuk informasi selengkapnya, lihat Mengonfigurasi ASP.NET Core untuk bekerja dengan server proxy dan memuat penyeimbang.
Sumber Daya Tambahan:
- aplikasi SocialSample rekayasa dotnet/AspNetCore: Aplikasi sampel yang ditautkan ada di cabang teknik repo
main
GitHub dotnet/AspNetCore. Cabangmain
berisi kode dalam pengembangan aktif untuk rilis ASP.NET Core berikutnya. Untuk melihat versi aplikasi sampel untuk versi ASP.NET Core yang dirilis, gunakan daftar drop-down Cabang untuk memilih cabang rilis (misalnyarelease/{X.Y}
).
ASP.NET Core