如何在Azure B2C中阻止未激活用户登录(Angular8+.NET Core场景)
你现在的做法确实会带来糟糕的用户体验——用户明明完成了登录流程,却被立刻登出,感觉像是系统出了问题。我们可以从身份验证流程的源头入手,在用户拿到登录令牌之前就拦截,而不是事后补救。下面是几个优先级从高到低的最优方案:
方案一:使用Azure B2C自定义策略(推荐)
这是最彻底的解决方案,直接在Azure B2C的身份验证流程中检查用户的extension_isActive属性,不符合条件的用户根本无法完成登录,连令牌都拿不到。
实现步骤:
- 在自定义策略中添加用户属性读取步骤
在你的B2C自定义策略的UserJourney节点里,添加一个OrchestrationStep,用于读取用户的extension_isActive自定义属性:<OrchestrationStep Order="3" Type="ClaimsExchange"> <Preconditions> <Precondition Type="ClaimsExist" ExecuteActionsIf="false"> <Value>extension_isActive</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> </Preconditions> <ClaimsExchanges> <ClaimsExchange Id="AADUserReadWithObjectId" TechnicalProfileReferenceId="AAD-UserReadUsingObjectId" /> </ClaimsExchanges> </OrchestrationStep> - 添加验证步骤,阻止非激活用户
紧接着上面的步骤,添加一个验证步骤,检查extension_isActive是否为true,如果不是则终止流程并返回错误:<OrchestrationStep Order="4" Type="ClaimsExchange"> <Preconditions> <Precondition Type="ClaimEquals" ExecuteActionsIf="true"> <Value>extension_isActive</Value> <Value>True</Value> <Action>SkipThisOrchestrationStep</Action> </Precondition> </Preconditions> <ClaimsExchanges> <ClaimsExchange Id="BlockInactiveUser" TechnicalProfileReferenceId="BlockInactiveUserProfile" /> </ClaimsExchanges> </OrchestrationStep> - 定义错误提示的TechnicalProfile
创建一个返回错误页面的TechnicalProfile,告诉用户需要等待管理员授权:<TechnicalProfile Id="BlockInactiveUserProfile"> <DisplayName>Block Inactive User</DisplayName> <Protocol Name="Proprietary" Handler="Web.TPEngine.Providers.SelfAssertedAttributeProvider, Web.TPEngine, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> <Metadata> <Item Key="ContentDefinitionReferenceId">api.error</Item> <Item Key="UserMessageIfClaimsTransformationBooleanValueIsNotEqual">Your account is pending admin authorization. Please contact support for assistance.</Item> </Metadata> <OutputClaims> <OutputClaim ClaimTypeReferenceId="extension_isActive" /> </OutputClaims> </TechnicalProfile>
这样,非激活用户在登录时会被直接导向B2C的错误提示页面,根本不会进入你的应用。
方案二:后端API层面拦截(次选)
如果暂时不想修改B2C自定义策略,可以在你的.NET Core后端API中添加全局授权检查,确保只有isActive=true的用户能访问API,同时前端在登录后先验证权限,再进入应用。
后端实现:
- 创建自定义授权策略
在Startup.cs(或Program.cs,取决于.NET Core版本)中添加自定义授权要求:services.AddAuthorization(options => { options.AddPolicy("ActiveUser", policy => policy.RequireClaim("extension_isActive", "true")); }); - 全局应用策略
把这个策略应用到所有API端点:app.UseEndpoints(endpoints => { endpoints.MapControllers().RequireAuthorization("ActiveUser"); });
或者在控制器/方法上添加[Authorize(Policy = "ActiveUser")]属性。
前端改进:
在MSAL登录成功后,先调用一个简单的API端点(比如/api/auth/check-active)来验证权限,而不是直接进入应用组件:
subscribeToBroadCastServiceOnLogin() { this.broadcastService.subscribe("msal:loginSuccess", async (success) => { try { const response = await this.http.get('/api/auth/check-active').toPromise(); // 验证通过,进入应用 this.router.navigate(['/dashboard']); } catch (error) { // 验证失败,提示并登出 window.alert("Your login is awaiting authorization from site Admin"); this.authService.logout(); } }); }
这个方案比你原来的做法体验更好,因为用户还没进入应用就会被拦截,而且后端API也能确保只有激活用户能访问资源。
方案三:前端提前检查令牌声明(临时方案)
如果以上两种方案都暂时无法实施,可以在MSAL登录成功后,立刻解析ID Token的声明并判断,在进入组件前就处理:
subscribeToBroadCastServiceOnLogin() { this.broadcastService.subscribe("msal:loginSuccess", (success) => { const isActive = success.idToken.claims["extension_3datttttxxxxxxxxxxxxxxxxxxxxxxxxx_isActive"]; if (!isActive) { window.alert("Your login is awaiting authorization from site Admin"); this.authService.logout(); // 阻止路由跳转 this.router.navigate(['/unauthorized']); return; } // 正常进入应用 this.router.navigate(['/dashboard']); }); }
这个方案只是比你原来的做法提前了拦截时机,但本质还是用户完成登录后再被登出,体验不如前两个方案。
总结一下,自定义策略是最优解,它从身份验证的源头阻止非激活用户,体验最好也最安全;后端拦截是可靠的次选,能同时保护API和优化前端体验;前端提前检查是临时过渡方案。
内容的提问来源于stack exchange,提问作者rumi

