Microsoft。Identity.Web 為與 Microsoft Entra ID 整合的 ASP.NET Core 應用程式提供安全的認證與授權預設值。 你可以自訂許多認證行為的面向,同時保留函式庫內建的安全功能。
識別可自訂區域
| 適用範圍 | 自訂選項 |
|---|---|
| Configuration | 所有 MicrosoftIdentityOptions、 OpenIdConnectOptions、 JwtBearerOptions 屬性 |
| 活動 | OpenID Connect 事件(OnTokenValidated、 OnRedirectToIdentityProvider等) |
| 代幣獲取 | 相關性 ID,額外查詢參數 |
| 宣告 | 新增自訂宣告至 ClaimsPrincipal |
| UI(使用者介面) | 登出頁面、重定向行為 |
| 登入 | 登入提示、網域提示 |
選擇客製化方式
下表總結了你可以自訂的區域以及每個區域所支援的內容。
可採用兩種方式之一來自訂選項:
-
Configure<TOptions>- 在選項使用前設定 -
PostConfigure<TOptions>- 在所有Configure呼叫完成後設定選項
執行順序:
Configure → Configure → ... → PostConfigure → PostConfigure → ... → Options used
設定認證選項
本節說明如何配置 Microsoft.Identity.Web 使用的各種認證選項類別。
理解配置映射
"AzureAd"區段在appsettings.json映射到多個類別:
你可以在配置中使用這些類別中的任何屬性。
模式一:配置 MicrosoftIdentityOptions
以下程式碼可 MicrosoftIdentityOptions 自訂啟用個人識別資訊(PII)記錄、設定用戶端功能及調整令牌驗證參數:
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();
模式二:配置 OpenIdConnectOptions(網頁應用程式)
以下程式碼可 OpenIdConnectOptions 自訂網頁應用程式,以設定回應類型、新增範圍,以及配置 Cookie 與令牌驗證設定:
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;
});
模式 3:配置 JwtBearerOptions(網頁 API)
以下程式碼可 JwtBearerOptions 自訂網路 API 以設定有效受眾、聲明映射及代幣存續驗證:
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
});
模式 4:設定 Cookie 選項
以下程式碼配置您的應用程式的 Cookie 政策與 Cookie 認證選項,包括安全設定與過期行為:
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;
});
自訂事件處理程式
OpenID Connect 和 JWT 持有者認證會公開一些事件,讓你可以連接和使用。 Microsoft。Identity.Web 會自行設定事件處理程序,因此你必須將自訂處理程序與現有的串接,以保留內建功能。
保留現有的處理者
當你新增自訂事件處理器時,務必先儲存並呼叫現有的處理器。 以下範例展示了錯誤與正確的方法。
以下程式碼 不正確地 覆蓋 Microsoft.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;
};
});
以下 程式碼正確 地與現有處理器串接:
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"));
};
});
應用常見事件情境
憑證驗證後新增自訂聲明
以下程式碼在令牌驗證之後的網路 API 中新增自訂聲明 ClaimsPrincipal。 它會從資料庫查詢使用者的部門,並根據電子郵件網域指派應用程式特定的角色:
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"));
}
};
});
以下程式碼透過呼叫 Microsoft Graph,在令牌驗證後取得更多使用者資料,在網頁應用程式中新增自訂權利要求:
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 ?? ""));
};
});
在授權請求中新增查詢參數
以下程式碼為發送給 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"];
}
};
});
自訂認證失敗處理
以下程式碼透過記錄錯誤並回傳自訂的 JSON 錯誤回應來處理認證失敗:
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
};
});
處理存取拒絕
以下程式碼會在用戶拒絕同意時,將重新導向至自訂頁面:
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;
};
});
自訂代幣取得方式
你可以透過將選項傳遞給 IDownstreamApi,來自訂呼叫下游 API 時如何取得令牌。
使用 IDownstreamApi 並自訂選項
以下程式碼在透過 IDownstreamApi 取得令牌時會傳遞關聯 ID 及額外查詢參數:
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);
}
}
自訂 UI
你可以控制用戶登入與登出後的停留地點,並自訂登出體驗。
登入後會重新導向到特定頁面
請使用 redirectUri 參數將使用者在登入後導向特定頁面:
<!-- 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"
});
}
自訂登出頁面
選項一:覆寫剃刀頁面
建立一個包含自訂內容的檔案:Areas/MicrosoftIdentity/Pages/Account/SignedOut.cshtml
@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>
選項二:重定向至自訂頁面
以下程式碼將使用者重新導向至自訂登出頁面,而非預設頁面:
builder.Services.Configure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options =>
{
options.Events.OnSignedOutCallbackRedirect = context =>
{
context.Response.Redirect("/Home/SignedOut");
context.HandleResponse();
return Task.CompletedTask;
};
});
自訂登入體驗
使用登入提示和網域提示
透過預先填入使用者名稱並引導使用者到特定的 Microsoft Entra 租戶,簡化登入體驗。
理解提示
| Hint | Purpose | 範例 |
|---|---|---|
| 登入提示 | 預先填入使用者名稱/電子郵件欄位 | "user@contoso.com" |
| domainHint | 直接前往特定租戶登入頁面 | "contoso.com" |
套用提示模式
模式一:控制器式
以下程式碼顯示標準登入、帶登入提示的登入、網域提示或兩者皆有的控制器操作:
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"
});
}
}
模式二:基於視圖
以下 HTML 顯示了具有不同提示配置的登入連結:
<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>
模式三:程式設計與 OnRedirectToIdentityProvider
以下程式碼在重定向至身份提供者時,根據查詢參數與 Cookie 動態設定提示:
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;
}
};
});
應用案例
電子商務平台:
// Pre-fill returning customer email
loginHint = customerEmail
B2B 申請:
// Direct to customer's tenant
domainHint = customerDomain
多租戶 SaaS:
// Route based on subdomain
domainHint = GetTenantFromSubdomain(Request.Host)
遵循最佳做法
應該做的事
1. 務必保留現有的事件處理程序。 在執行自訂邏輯前,先儲存並呼叫現有的處理器:
var existingHandler = options.Events.OnTokenValidated;
options.Events.OnTokenValidated = async context =>
{
await existingHandler(context); // Call Microsoft.Identity.Web's handler
// Your custom code
};
2. 使用相關識別碼進行追蹤。 將關聯 ID 附加到診斷用的令牌取得請求中。
var tokenOptions = new TokenAcquisitionOptions
{
CorrelationId = Activity.Current?.Id ?? Guid.NewGuid()
};
3. 驗證自定義宣告。 在授權存取前,請確認自訂權利要求是否包含預期值:
var department = context.Principal.FindFirst("department")?.Value;
if (!IsValidDepartment(department))
{
throw new UnauthorizedAccessException("Invalid department");
}
4. 日誌自訂錯誤。 將自訂邏輯包裝在 try-catch 區塊中,並記錄錯誤:
try
{
// Custom logic
}
catch (Exception ex)
{
logger.LogError(ex, "Custom authentication logic failed");
throw;
}
5. 測試成功與失敗的路徑。 在測試中涵蓋所有認證情境:
// Test with valid tokens
// Test with missing claims
// Test with expired tokens
// Test with wrong audience
禁忌事项
1。別跳過 Microsoft。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. 不要在生產環境中啟用個人識別資訊(PII)記錄:
// Wrong
options.EnablePiiLogging = true; // In production!
// Correct
if (builder.Environment.IsDevelopment())
{
options.EnablePiiLogging = true;
}
3. 不要繞過代幣驗證:
// Wrong - insecure!
options.TokenValidationParameters.ValidateLifetime = false;
options.TokenValidationParameters.ValidateAudience = false;
// Correct - maintain security
options.TokenValidationParameters.ValidateLifetime = true;
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(5);
4. 不要硬編碼敏感值:
// Wrong
options.ClientSecret = "mysecret123";
// Correct
options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
5. 不要修改中介軟體中的認證:
// Wrong - configure in Startup, not middleware
app.Use(async (context, next) =>
{
// Modifying auth options here is too late!
});
排除常見問題
解決自訂功能未生效
檢查執行順序:
-
AddMicrosoftIdentityWebApp/AddMicrosoftIdentityWebApi設定預設值 - 你的
Configure通話進行中 -
PostConfigure通話持續(如有) - 使用選項
解決方案:如果PostConfigure呼叫沒有生效,請使用Configure,因為PostConfigure是在所有Configure呼叫之後執行的。
services.PostConfigure<OpenIdConnectOptions>(
OpenIdConnectDefaults.AuthenticationScheme,
options => { /* your changes */ }
);
修正缺少的自訂聲明
如果沒有出現自訂聲明,請確認以下事項:
- 該
OnTokenValidated處理器與現有處理器正確串接。 - 認證成功後,你的程式碼才會新增聲明。
- 聲明會新增到正確的
ClaimsIdentity。
以下程式碼會紀錄所有用於除錯的宣告:
var claims = context.Principal.Claims.ToList();
logger.LogInformation($"Claims count: {claims.Count}");
foreach (var claim in claims)
{
logger.LogInformation($"{claim.Type}: {claim.Value}");
}
修正事件未觸發
如果事件沒有觸發,請確認認證和授權中介件的註冊順序是否正確:
app.UseAuthentication(); // Must be first
app.UseAuthorization(); // Must be second
app.MapControllers(); // Then endpoints