.NET 8 Blazor多应用同时登出及postlogout_redirect_uri安全配置问题
基于.NET 8的Blazor WASM与Server应用Azure AD B2C跨应用登出及安全防护方案
问题背景
现有两个基于.NET 8的应用:Blazor WASM(客户端)和Blazor Server(服务端),需实现用户在两个应用中同时登出,并保障postlogout_redirect_uri的安全性。
当前实现逻辑
1. 触发Azure AD B2C登出(Usermenu.razor.cs)
private async Task NavigateToLogout() { string idToken = ""; var authenticationResult = await TokenProvider.RequestAccessToken(); if (authenticationResult.TryGetToken(out var accessToken)) { idToken = accessToken.Value; } string postLogoutRedirectUri = Configuration.GetValue<string>("AzureAdB2c:logoutEndpoint"); string authority = Configuration.GetValue<string>("AzureAdB2c:Authority"); var logoutUrl = $"{authority}/oauth2/v2.0/logout?" + $"post_logout_redirect_uri={Uri.EscapeDataString(postLogoutRedirectUri)}&" + $"id_token_hint={Uri.EscapeDataString(idToken)}"; AppState.NavigationManager.NavigateTo(logoutUrl); }
其中postLogoutRedirectUri配置为https://localhost:5007/logoutendpoint,已在服务端应用注册中添加该地址。
2. 服务端登出端点(LogoutEndpoint.razor.cs)
public partial class LogoutEndpoint { [Inject] AppState AppState { get; set; } private bool _isLoading { get; set; } = false; protected override void OnInitialized() { _isLoading = true; AppState.NavigationManager.NavigateToLogout("authentication/logout"); _isLoading = false; } }
遇到的问题
- 开启B2C策略配置
<SingleSignOn Scope="Policy" EnforceIdTokenHintOnLogout="true" />时,第二次登出请求因缺少合法的id_token_hint被拦截; - 关闭该配置后,用户登出后再次登录会循环跳转到
/logoutendpoint,最终回到登录页。
解决方案
一、实现跨应用同时登出
1. 修正id_token_hint获取逻辑(核心错误修复)
当前代码误用Access Token作为id_token_hint,需改为获取ID Token(Azure AD B2C要求id_token_hint必须是用户的ID Token):
private async Task NavigateToLogout() { string idToken = ""; // 获取当前用户的ID Token(以MSAL为例) var accounts = await _msalClient.GetAccountsAsync(); if (accounts.Any()) { var silentResult = await _msalClient.AcquireTokenSilent(new[] { "openid" }, accounts.First()) .ExecuteAsync(); idToken = silentResult.IdToken; } string postLogoutRedirectUri = Configuration.GetValue<string>("AzureAdB2c:logoutEndpoint"); string authority = Configuration.GetValue<string>("AzureAdB2c:Authority"); var logoutUrl = $"{authority}/oauth2/v2.0/logout?" + $"post_logout_redirect_uri={Uri.EscapeDataString(postLogoutRedirectUri)}&" + $"id_token_hint={Uri.EscapeDataString(idToken)}"; AppState.NavigationManager.NavigateTo(logoutUrl); }
2. 跨应用本地登出触发
Azure AD B2C的全局登出只会清除自身会话,需手动触发两个应用的本地会话清除:
- 在服务端
LogoutEndpoint页面完成自身登出后,通过隐藏iframe加载客户端登出端点,触发Blazor WASM的本地登出:@if (_isLoading) { <p>正在登出...</p> } else { <!-- 隐藏iframe触发客户端登出 --> <iframe src="https://localhost:xxxx/authentication/logout" style="display:none"></iframe> <p>登出完成,即将跳转首页...</p> } - 服务端登出逻辑添加身份状态校验,避免重复执行登出:
[Inject] AuthenticationStateProvider AuthStateProvider { get; set; } protected override async Task OnInitializedAsync() { _isLoading = true; var authState = await AuthStateProvider.GetAuthenticationStateAsync(); if (!authState.User.Identity.IsAuthenticated) { // 用户已登出,直接跳转首页 AppState.NavigationManager.NavigateTo("/"); _isLoading = false; return; } // 执行服务端本地登出 AppState.NavigationManager.NavigateToLogout("authentication/logout"); _isLoading = false; }
二、保障postlogout_redirect_uri安全性
1. 依赖Azure AD B2C内置校验
在Azure AD B2C应用注册的身份验证页面,仅添加可信的post_logout_redirect_uri地址。Azure AD B2C会自动校验请求中的地址是否在允许列表内,非法地址会被直接拒绝。
2. 添加state参数防篡改
构造登出URL时生成随机state值并存储,在登出回调页面校验该值,防止CSRF攻击和地址篡改:
- 触发登出时生成并存储
state:private async Task NavigateToLogout() { // ... 省略ID Token获取逻辑 ... string state = Guid.NewGuid().ToString(); // 存储state(服务端用Session,客户端用SessionStorage) AppState.NavigationManager.SessionStorage.SetItemAsString("logoutState", state); var logoutUrl = $"{authority}/oauth2/v2.0/logout?" + $"post_logout_redirect_uri={Uri.EscapeDataString(postLogoutRedirectUri)}&" + $"id_token_hint={Uri.EscapeDataString(idToken)}&" + $"state={Uri.EscapeDataString(state)}"; AppState.NavigationManager.NavigateTo(logoutUrl); } - 在
LogoutEndpoint页面校验state:protected override void OnInitialized() { _isLoading = true; // 解析返回的state参数 var queryParams = new Uri(AppState.NavigationManager.Uri).ParseQueryString(); string returnedState = queryParams["state"]; string storedState = AppState.NavigationManager.SessionStorage.GetItemAsString("logoutState"); // 校验state合法性 if (string.IsNullOrWhiteSpace(returnedState) || !returnedState.Equals(storedState)) { AppState.NavigationManager.NavigateTo("/error"); _isLoading = false; return; } // 清除存储的state AppState.NavigationManager.SessionStorage.RemoveItem("logoutState"); // ... 省略登出逻辑 ... }
3. 保留EnforceIdTokenHintOnLogout=true配置
该配置强制要求登出请求携带合法的id_token_hint,能有效防止未经授权的恶意登出请求,修正id_token_hint获取逻辑后即可正常使用。
内容的提问来源于stack exchange,提问作者Leroy Gabriëls
相关产品推荐
相关产品推荐

