Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Microsoft. Identity.Web menyediakan default aman untuk autentikasi dan otorisasi dalam aplikasi ASP.NET Core yang terintegrasi dengan Microsoft Entra ID. Anda dapat menyesuaikan banyak aspek perilaku autentikasi sambil mempertahankan fitur keamanan bawaan pustaka.
Mengidentifikasi area yang dapat disesuaikan
| Wilayah | Opsi Kustomisasi |
|---|---|
| Configuration | Semua MicrosoftIdentityOptions, OpenIdConnectOptions, JwtBearerOptions properti |
| Peristiwa | Peristiwa OpenID Connect (OnTokenValidated, OnRedirectToIdentityProvider, dll.) |
| Akuisisi Token | ID korelasi, parameter kueri tambahan |
| Klaim | Menambahkan klaim kustom ke ClaimsPrincipal |
| UI | Halaman keluar akun, perilaku pengalihan |
| Masuk | Petunjuk masuk, petunjuk domain |
Pilih metode kustomisasi
Tabel berikut ini meringkas area yang bisa Anda kustomisasi dan apa yang didukung setiap area.
Gunakan salah satu dari dua pendekatan untuk menyesuaikan opsi:
-
Configure<TOptions>- Mengonfigurasi opsi sebelum digunakan -
PostConfigure<TOptions>- Mengonfigurasi opsi setelah semua panggilanConfigure
Urutan eksekusi:
Configure → Configure → ... → PostConfigure → PostConfigure → ... → Options used
Konfigurasi pilihan autentikasi
Bagian ini memperlihatkan cara mengonfigurasi berbagai kelas opsi autentikasi yang Microsoft. Identity.Web menggunakan.
Memahami pemetaan konfigurasi
Bagian "AzureAd" dalam appsettings.json memetakan ke beberapa kelas:
Anda dapat menggunakan properti apa pun dari kelas ini dalam konfigurasi Anda.
Pola 1: Mengonfigurasi MicrosoftIdentityOptions
Kode berikut menyesuaikan MicrosoftIdentityOptions untuk mengaktifkan pengelogan PII, mengatur kemampuan klien, dan menyesuaikan parameter validasi token:
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
// Customize Microsoft Identity options
builder.Services.Configure<MicrosoftIdentityOptions>(options =>
{
// Enable PII logging (development only!)
options.EnablePiiLogging = true;
// Custom client capabilities
options.ClientCapabilities = new[] { "CP1", "CP2" };
// Override token validation parameters
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
});
var app = builder.Build();
Pola 2: Mengonfigurasi OpenIdConnectOptions (Aplikasi web)
Kode berikut menyesuaikan OpenIdConnectOptions aplikasi web untuk mengatur jenis respons, menambahkan cakupan, dan mengonfigurasi pengaturan validasi cookie dan token:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
// Customize OpenIdConnect options
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
// Override response type
options.ResponseType = "code id_token";
// Add extra scopes
options.Scope.Add("offline_access");
options.Scope.Add("profile");
// Customize token validation
options.TokenValidationParameters.NameClaimType = "preferred_username";
options.TokenValidationParameters.RoleClaimType = "roles";
// Set redirect URI
options.CallbackPath = "/signin-oidc";
// Configure cookie options
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
});
Pola 3: Mengonfigurasi JwtBearerOptions (API Web)
Kode berikut menyesuaikan JwtBearerOptions API web untuk mengatur audiens, pemetaan klaim, dan validasi masa pakai token yang valid:
using Microsoft.AspNetCore.Authentication.JwtBearer;
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
// Customize JWT Bearer options
builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
// Customize audience validation
options.TokenValidationParameters.ValidAudiences = new[]
{
"api://your-api-client-id",
"https://your-api.com"
};
// Set custom claim mappings
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "roles";
// Customize token validation
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.Zero; // No tolerance
});
Pola 4: Mengonfigurasi opsi Cookie
Kode berikut mengonfigurasi kebijakan cookie dan opsi autentikasi cookie untuk aplikasi Anda, termasuk pengaturan keamanan dan perilaku kedaluwarsa:
using Microsoft.AspNetCore.Authentication.Cookies;
// Configure cookie policy
builder.Services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.Lax;
options.Secure = CookieSecurePolicy.Always;
options.HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always;
});
// Configure cookie authentication options
builder.Services.Configure<CookieAuthenticationOptions>(
CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.Cookie.Name = "MyApp.Auth";
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Lax;
options.ExpireTimeSpan = TimeSpan.FromHours(1);
options.SlidingExpiration = true;
});
Mengkustomisasi penanganan aktivitas
Autentikasi OpenID Connect dan JWT Bearer mengekspos peristiwa yang dapat dihubungkan. Microsoft. Identity.Web menyiapkan penanganan aktivitasnya sendiri, jadi Anda harus menautkan handler kustom Anda dengan yang sudah ada untuk mempertahankan fungsionalitas bawaan.
Pertahankan handler yang ada
Saat Anda menambahkan penanganan aktivitas kustom, selalu simpan dan panggil handler yang ada terlebih dahulu. Contoh berikut menunjukkan pendekatan yang salah dan benar.
Kode berikut salah menimpa Microsoft. Handler Identity.Web:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Events.OnTokenValidated = async context =>
{
// Your code - but you LOST the built-in validation!
await Task.CompletedTask;
};
});
Kode berikut ditautkan dengan benar dengan handler yang ada:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
// Call Microsoft.Identity.Web's handler FIRST
await existingOnTokenValidatedHandler(context);
// Then your custom code
// (executes AFTER built-in security checks)
var identity = context.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("custom_claim", "custom_value"));
};
});
Menerapkan skenario peristiwa umum
Menambahkan klaim kustom setelah validasi token
Kode berikut menambahkan klaim kustom ke ClaimsPrincipal setelah validasi token dalam API web. Ini mencari departemen pengguna dari database dan menetapkan peran khusus aplikasi berdasarkan domain email:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Security.Claims;
builder.Services.Configure<JwtBearerOptions>(
JwtBearerDefaults.AuthenticationScheme,
options =>
{
var existingHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
// Preserve built-in validation
await existingHandler(context);
// Add custom claims
var identity = context.Principal.Identity as ClaimsIdentity;
// Example: Add department claim from database
var userObjectId = context.Principal.FindFirst("oid")?.Value;
if (!string.IsNullOrEmpty(userObjectId))
{
var department = await GetUserDepartment(userObjectId);
identity?.AddClaim(new Claim("department", department));
}
// Example: Add application-specific role
var email = context.Principal.FindFirst("email")?.Value;
if (email?.EndsWith("@admin.com") == true)
{
identity?.AddClaim(new Claim(ClaimTypes.Role, "SuperAdmin"));
}
};
});
Kode berikut menambahkan klaim kustom di aplikasi web dengan memanggil Microsoft Graph untuk mengambil data profil pengguna tambahan setelah validasi token:
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
var existingHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
// Preserve built-in processing
await existingHandler(context);
// Call Microsoft Graph to get additional user data
var graphClient = context.HttpContext.RequestServices
.GetRequiredService<GraphServiceClient>();
var user = await graphClient.Me.GetAsync();
var identity = context.Principal.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("jobTitle", user?.JobTitle ?? ""));
identity?.AddClaim(new Claim("department", user?.Department ?? ""));
};
});
Menambahkan parameter kueri ke permintaan otorisasi
Kode berikut menambahkan parameter kueri kustom ke permintaan otorisasi yang dikirim ke penyedia identitas Microsoft Entra:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
var existingHandler = options.Events.OnRedirectToIdentityProvider;
options.Events.OnRedirectToIdentityProvider = async context =>
{
// Preserve existing behavior
if (existingHandler != null)
{
await existingHandler(context);
}
// Add custom query parameters
context.ProtocolMessage.Parameters.Add("slice", "testslice");
context.ProtocolMessage.Parameters.Add("custom_param", "custom_value");
// Conditional parameters based on request
if (context.HttpContext.Request.Query.ContainsKey("prompt"))
{
context.ProtocolMessage.Prompt = context.HttpContext.Request.Query["prompt"];
}
};
});
Menyesuaikan penanganan kegagalan autentikasi
Kode berikut menangani kegagalan autentikasi dengan mencatat kesalahan dan mengembalikan respons kesalahan JSON kustom:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
options.Events.OnAuthenticationFailed = async context =>
{
// Log the error
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
logger.LogError(context.Exception, "Authentication failed");
// Customize error response
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync($$"""
{
"error": "authentication_failed",
"error_description": "{{context.Exception.Message}}"
}
""");
context.HandleResponse(); // Suppress default error handling
};
});
Menangani akses ditolak
Kode berikut mengalihkan pengguna ke halaman kustom saat mereka menolak persetujuan:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
options.Events.OnAccessDenied = async context =>
{
// User denied consent
context.Response.Redirect("/Home/AccessDenied");
context.HandleResponse();
await Task.CompletedTask;
};
});
Menyesuaikan akuisisi token
Anda dapat menyesuaikan bagaimana token diperoleh saat memanggil API hilir dengan meneruskan opsi ke IDownstreamApi.
Menggunakan IDownstreamApi dengan opsi kustom
Kode berikut meneruskan ID korelasi dan parameter kueri tambahan saat memperoleh token melalui IDownstreamApi:
using Microsoft.Identity.Abstractions;
public class TodoListController : ControllerBase
{
private readonly IDownstreamApi _downstreamApi;
public TodoListController(IDownstreamApi downstreamApi)
{
_downstreamApi = downstreamApi;
}
[HttpGet("{id}")]
public async Task<ActionResult> GetTodo(int id, Guid correlationId)
{
var result = await _downstreamApi.GetForUserAsync<Todo>(
"TodoListService",
options =>
{
options.RelativePath = $"api/todolist/{id}";
// Customize token acquisition
options.TokenAcquisitionOptions = new TokenAcquisitionOptions
{
CorrelationId = correlationId,
ExtraQueryParameters = new Dictionary<string, string>
{
{ "slice", "test_slice" }
}
};
});
return Ok(result);
}
}
Menyesuaikan UI
Anda dapat mengontrol di mana pengguna mendarat setelah masuk dan keluar, serta menyesuaikan pengalaman setelah keluar.
Mengalihkan ke halaman tertentu setelah masuk
redirectUri Gunakan parameter untuk mengirim pengguna ke halaman tertentu setelah mereka masuk:
<!-- Razor view -->
<a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard">Sign In</a>
<!-- Or in controller -->
[HttpGet]
public IActionResult SignInToDashboard()
{
return RedirectToAction("SignIn", "Account", new
{
area = "MicrosoftIdentity",
redirectUri = "/Dashboard"
});
}
Mengkustomisasi halaman setelah keluar
Opsi 1: Ambil alih Halaman Razor
Buat file di Areas/MicrosoftIdentity/Pages/Account/SignedOut.cshtml dengan konten kustom Anda:
@page
@model Microsoft.Identity.Web.UI.Areas.MicrosoftIdentity.Pages.Account.SignedOutModel
@{
ViewData["Title"] = "Signed out";
}
<div class="container text-center mt-5">
<h1>You have been signed out</h1>
<p>Thank you for using our application.</p>
<a asp-area="" asp-controller="Home" asp-action="Index" class="btn btn-primary">
Return to Home
</a>
</div>
Opsi 2: Mengalihkan ke halaman kustom
Kode berikut mengalihkan pengguna ke halaman keluar kustom alih-alih default:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
options.Events.OnSignedOutCallbackRedirect = context =>
{
context.Response.Redirect("/Home/SignedOut");
context.HandleResponse();
return Task.CompletedTask;
};
});
Mengkustomisasi pengalaman masuk
Menggunakan petunjuk masuk dan petunjuk domain
Sederhanakan pengalaman masuk dengan mengisi nama pengguna sebelumnya dan mengarahkan pengguna ke penyewa Microsoft Entra tertentu.
Memahami petunjuk
| Petunjuk | Kegunaan | Example |
|---|---|---|
| loginHint | Bidang nama pengguna/email diisi sebelumnya | "user@contoso.com" |
| domainHint | Langsung ke halaman masuk penyewa tertentu | "contoso.com" |
Menerapkan pola petunjuk
Pola 1: Berbasis pengontrol
Kode berikut menunjukkan tindakan pengontrol untuk proses masuk standar, masuk dengan petunjuk login, petunjuk domain, atau keduanya:
using Microsoft.AspNetCore.Mvc;
public class AuthController : Controller
{
[HttpGet]
public IActionResult SignIn()
{
// Standard sign-in
return RedirectToAction("SignIn", "Account", new
{
area = "MicrosoftIdentity",
redirectUri = "/Dashboard"
});
}
[HttpGet]
public IActionResult SignInWithLoginHint()
{
// Pre-populate username
return RedirectToAction("SignIn", "Account", new
{
area = "MicrosoftIdentity",
redirectUri = "/Dashboard",
loginHint = "user@contoso.com"
});
}
[HttpGet]
public IActionResult SignInWithDomainHint()
{
// Direct to Contoso tenant
return RedirectToAction("SignIn", "Account", new
{
area = "MicrosoftIdentity",
redirectUri = "/Dashboard",
domainHint = "contoso.com"
});
}
[HttpGet]
public IActionResult SignInWithBothHints()
{
// Pre-populate AND direct to tenant
return RedirectToAction("SignIn", "Account", new
{
area = "MicrosoftIdentity",
redirectUri = "/Dashboard",
loginHint = "user@contoso.com",
domainHint = "contoso.com"
});
}
}
Pola 2: Berbasis tampilan
HTML berikut menunjukkan tautan masuk dengan konfigurasi petunjuk yang berbeda:
<div class="sign-in-options">
<h2>Sign In Options</h2>
<!-- Standard sign-in -->
<a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard"
class="btn btn-primary">
Sign In
</a>
<!-- With login hint -->
<a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard&loginHint=user@contoso.com"
class="btn btn-secondary">
Sign In as user@contoso.com
</a>
<!-- With domain hint -->
<a href="/MicrosoftIdentity/Account/SignIn?redirectUri=/Dashboard&domainHint=contoso.com"
class="btn btn-secondary">
Sign In (Contoso)
</a>
</div>
Pola 3: Pemrograman dengan OnRedirectToIdentityProvider
Kode berikut secara dinamis menetapkan petunjuk berdasarkan parameter kueri dan cookie selama pengalihan ke penyedia identitas:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
var existingHandler = options.Events.OnRedirectToIdentityProvider;
options.Events.OnRedirectToIdentityProvider = async context =>
{
if (existingHandler != null)
{
await existingHandler(context);
}
// Add hints based on application logic
if (context.HttpContext.Request.Query.TryGetValue("tenant", out var tenant))
{
context.ProtocolMessage.DomainHint = tenant;
}
// Get suggested user from cookie or session
var suggestedUser = context.HttpContext.Request.Cookies["LastSignedInUser"];
if (!string.IsNullOrEmpty(suggestedUser))
{
context.ProtocolMessage.LoginHint = suggestedUser;
}
};
});
Skenario penggunaan
Platform E-niaga:
// Pre-fill returning customer email
loginHint = customerEmail
Aplikasi B2B:
// Direct to customer's tenant
domainHint = customerDomain
Multi-Tenant SaaS:
// Route based on subdomain
domainHint = GetTenantFromSubdomain(Request.Host)
Ikuti praktik terbaik
Yang Harus Dilakukan
1. Selalu pertahankan penanganan aktivitas yang ada. Simpan dan panggil handler yang ada sebelum menjalankan logika kustom Anda:
var existingHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingHandler(context); // Call Microsoft.Identity.Web's handler
// Your custom code
};
2. Gunakan ID korelasi untuk pelacakan. Lampirkan ID korelasi ke permintaan akuisisi token untuk diagnostik:
var tokenOptions = new TokenAcquisitionOptions
{
CorrelationId = Activity.Current?.Id ?? Guid.NewGuid()
};
3. Memvalidasi klaim khusus. Verifikasi bahwa klaim kustom berisi nilai yang diharapkan sebelum memberikan akses:
var department = context.Principal.FindFirst("department")?.Value;
if (!IsValidDepartment(department))
{
throw new UnauthorizedAccessException("Invalid department");
}
Kesalahan kustomisasi log. Bungkus logika kustom dalam blok try-catch dan catat kesalahan.
try
{
// Custom logic
}
catch (Exception ex)
{
logger.LogError(ex, "Custom authentication logic failed");
throw;
}
5. Uji jalur keberhasilan dan kegagalan. Mencakup semua skenario autentikasi dalam pengujian Anda:
// Test with valid tokens
// Test with missing claims
// Test with expired tokens
// Test with wrong audience
Hal-hal yang Tidak Boleh Dilakukan
1. Jangan lewati Microsoft. Penanganan aktivitas Identity.Web:
// Wrong - loses built-in security checks
options.Events.OnTokenValidated = async context => { /* your code */ };
// Correct - preserves security
var existing = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existing(context);
/* your code */
};
2. Jangan aktifkan pencatatan log PII dalam produksi:
// Wrong
options.EnablePiiLogging = true; // In production!
// Correct
if (builder.Environment.IsDevelopment())
{
options.EnablePiiLogging = true;
}
3. Jangan melewati validasi token:
// Wrong - insecure!
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateAudience = false;
// Correct - maintain security
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
4. Jangan hardcode nilai sensitif:
// Wrong
options.ClientSecret = "mysecret123";
// Correct
options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
5. Jangan ubah autentikasi di middleware:
// Wrong - configure in Startup, not middleware
app.Use(async (context, next) =>
{
// Modifying auth options here is too late!
});
Pemecahan Masalah Umum
Mengatasi permasalahan penyesuaian tidak diterapkan
Periksa urutan eksekusi:
-
AddMicrosoftIdentityWebApp/AddMicrosoftIdentityWebApimengatur default - Panggilan Anda
Configuresedang berlangsung -
PostConfigurepanggilan berjalan (jika ada) - Opsi digunakan
Solusi: Gunakan PostConfigure jika panggilan Anda Configure tidak berlaku, karena PostConfigure berjalan setelah semua Configure panggilan:
services.PostConfigure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options => { /* your changes */ }
);
Memperbaiki klaim kustom yang hilang
Verifikasi hal berikut jika klaim kustom tidak muncul:
- Handler
OnTokenValidatedditautkan dengan benar dengan handler yang ada. - Autentikasi berhasil sebelum kode Anda menambahkan klaim.
- Klaim ditambahkan ke
ClaimsIdentityyang benar.
Kode berikut merekam semua klaim untuk pendebugan.
var claims = context.Principal.Claims.ToList();
logger.LogInformation($"Claims count: {claims.Count}");
foreach (var claim in claims)
{
logger.LogInformation($"{claim.Type}: {claim.Value}");
}
Memperbaiki peristiwa yang tidak diaktifkan
Jika peristiwa tidak diaktifkan, verifikasi bahwa middleware autentikasi dan otorisasi terdaftar dalam urutan yang benar:
app.UseAuthentication(); // Must be first
app.UseAuthorization(); // Must be second
app.MapControllers(); // Then endpoints