在 Blazor WebAssembly 中集成 MSAL:Microsoft.Authentication.WebAssembly.Msal 包实战与源码解析
【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore
Microsoft.Authentication.WebAssembly.Msal是 ASP.NET Core 仓库中面向Blazor WebAssembly(客户端渲染)应用的认证包,它封装了微软的 MSAL.js 库,为运行在浏览器沙箱中的 Blazor 应用提供基于Azure Active Directory / Azure AD B2C的纯客户端登录能力。本文以该包的官方说明(PACKAGE.md)为骨架,结合仓库内真实源码与项目模板,完整讲解安装方式、AddMsalAuthentication接入方法、MsalProviderOptions/MsalAuthenticationOptions/MsalCacheOptions三大配置模型的全部选项与默认值,并深入解释默认值如何被后置配置器落地执行。读完你将能独立在 WASM 应用中完成从安装、配置到登录交互组件的全套接入,并理解底层配置机制。
包定位:为什么 Blazor WebAssembly 需要 MSAL
Blazor WebAssembly 应用的所有代码(包括认证逻辑)都在浏览器端运行,无法像 Blazor Server 那样复用服务端会话与 Cookie 认证中间件。因此 .NET 团队以 MSAL.js 为底层引擎,提供了一组面向客户端的“远程认证”封装,其中针对微软 Azure AD / Azure AD B2C 身份源的就是本包。
从仓库结构可以清晰看到它的组成(目录 src/Components/WebAssembly/Authentication.Msal/src):
- C# 侧:
MsalWebAssemblyServiceCollectionExtensions.cs(DI 扩展入口)、三个 Models 文件(选项模型)、MsalDefaultOptionsConfiguration.cs(默认值后置配置器); - JS 互操作侧:Interop/AuthenticationService.ts 封装 MSAL.js 调用,通过 JSInterop 供 .NET 侧驱动登录、登出与令牌获取;
- 配套的 ILLink.Descriptors.xml 用于在发布裁剪(trimming)时保留上述选项类型,说明该包在设计上即面向 WebAssembly 裁剪后的精简运行时。
需要说明:该包仅负责客户端侧认证流程。若需要校验由它签发的令牌,服务端(托管该 WASM 应用的 ASP.NET Core 宿主)还需配合 JWT Bearer 认证来验证令牌,二者分工不同。
安装:一条 dotnet 命令
包说明给出的安装方式非常直接:
dotnet add package Microsoft.Authentication.WebAssembly.Msal该命令会把包引用写入项目文件(.csproj)。由于 Blazor WebAssembly 应用需要调用浏览器中的 MSAL.js 脚本,安装包后会一并带上经过打包的 JS 资源(对应仓库中的 Interop 目录及其package.json/rollup.config.mjs构建产物),应用只需正常发布即可携带这些静态资源,无需手工下载任何脚本。
注意:本包只适用于
Microsoft.AspNetCore.Components.WebAssembly客户端项目。Blazor Server 场景请使用服务端侧的认证方案;若是 Azure AD B2C 且希望借用微软提供的基础 UI 流程,也仍以本包配合RemoteAuthenticatorView使用。
接入:从 AddMsalAuthentication 说起
包说明将具体用法指向官方安全文档,而其编程入口在本仓库的 MsalWebAssemblyServiceCollectionExtensions.cs 中定义。该文件提供了三个泛型层层递进的重载:
// 1) 最简形式:使用默认 RemoteAuthenticationState 与 RemoteUserAccount services.AddMsalAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); }); // 2) 自定义认证状态类型 services.AddMsalAuthentication<RemoteAuthenticationState>(options => { /* ... */ }); // 3) 同时自定义状态类型与用户账户类型 services.AddMsalAuthentication<TRemoteAuthenticationState, TAccount>(options => { /* ... */ });其中带泛型的两个重载分别在类型参数上标注了[DynamicallyAccessedMembers(JsonSerialized)],并要求TRemoteAuthenticationState : RemoteAuthenticationState, new()、TAccount : RemoteUserAccount——这是为了让裁剪器知道这些类型会被 JSON 反序列化,避免发布后被剪掉导致状态还原失败。
从源码看,无论走哪个重载,最终都会落到第三个泛型实现(L54-L64),其内部只做两件事:
- 调用
AddRemoteAuthentication<TRemoteAuthenticationState, TAccount, MsalProviderOptions>(configure)注册通用的远程认证基础设施; - 通过
TryAddEnumerable注册MsalDefaultOptionsConfiguration作为IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>,用于兜底填充默认值。
AddRemoteAuthentication本身来自同仓库的基础认证抽象包(Microsoft.AspNetCore.Components.WebAssembly.Authentication),可见 MSAL 集成是建立在通用的“远程认证”框架之上的:认证提供方被抽象为MsalProviderOptions,UI 层使用通用的AuthorizeView、AuthorizeRouteView与RemoteAuthenticatorView,这也是为什么即便更换身份提供商(如切换到 OIDC),Blazor 组件代码可以保持一致。
配置模型一:MsalProviderOptions(ProviderOptions)
MsalProviderOptions(Models/MsalProviderOptions.cs)是传给 MSAL.js 的顶层配置,也是options.ProviderOptions的类型。它包含五个成员:
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Authentication | MsalAuthenticationOptions | RedirectUri与PostLogoutRedirectUri预置(见下节) | JSON 序列化名称为"auth",对应 MSAL.js 的 auth 配置块 |
Cache | MsalCacheOptions | CacheLocation = "sessionStorage",StoreAuthStateInCookie = false | 对应 MSAL.js 的 cache 配置块 |
DefaultAccessTokenScopes | IList<string> | 空集合 | 登录流程中默认申请的访问令牌作用域 |
AdditionalScopesToConsent | IList<string> | 空集合 | 首次登录时额外征求同意的作用域,用于跨资源授权 |
LoginMode | string | "popup" | 发起登录的交互模式 |
几个要点:
- LoginMode 默认是
popup(弹出窗口方式),而非整页重定向。若你的应用期望以redirect方式登录(例如需要精确控制页面跳转或被嵌入 iframe),可改为"redirect"。 DefaultAccessTokenScopes与AdditionalScopesToConsent的区别很实际:前者是登录成功后直接为已配置的 API 预取令牌,后者只是在同意页上一并征求其他资源的作用域许可,二者配合可显著减少后续静默获取令牌时的额外交互。- 其中
Authentication属性带有[JsonPropertyName("auth")],Cache也按 msal.js 的配置结构命名,说明这些选项最终会被逐项映射为 MSAL.js 构造参数,保持与上游库一致的语义。
配置模型二:MsalAuthenticationOptions(Authentication)
MsalAuthenticationOptions(Models/MsalAuthenticationOptions.cs)描述 MSAL.js 的 auth 块,是最常被配置的对象,典型的 appsettings.json 绑定写法如下:
{ "AzureAd": { "Authority": "https://login.microsoftonline.com/{tenantId}", "ClientId": "{client-id}", "ValidateAuthority": true } }builder.Services.AddMsalAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); });各属性语义与默认值如下:
| 属性 | 默认值 | 说明 |
|---|---|---|
ClientId | null(必填) | Azure AD / B2C 中应用注册的客户端 ID(Application / Client ID) |
Authority | null | Azure AD 或 Azure AD B2C 实例的颁发机构地址。B2C 场景通常为https://{tenant}.b2clogin.com/{tenant}/{policy}形式 |
ValidateAuthority | true | 是否校验 authority;使用 Azure AD B2C 时需显式置为false |
RedirectUri | 相对路径"authentication/login-callback" | 登录回跳地址,可为绝对或基于基地址的相对 URI |
PostLogoutRedirectUri | 相对路径"authentication/logout-callback" | 登出后回跳地址,同样支持相对或绝对 URI |
NavigateToLoginRequestUrl | false(由配置器强制) | 登录成功后是否回到发起登录时的 URL |
KnownAuthorities | 空集合 | 已知的 authority 主机名列表,用于 B2C 等多租户/自定义域场景辅助校验 |
其中ValidateAuthority = false的 B2C 约束、RedirectUri/PostLogoutRedirectUri相对路径默认值,都直接对应源码中的注释与初始化(见下节配置器实现)。
配置模型三:MsalCacheOptions(Cache)
MsalCacheOptions(Models/MsalCacheOptions.cs)控制令牌缓存的存放位置:
| 属性 | 默认值 | 说明 |
|---|---|---|
CacheLocation | "sessionStorage" | 令牌缓存存放位置,合法值为sessionStorage或localStorage |
StoreAuthStateInCookie | false | 是否同时把认证状态写入 Cookie |
默认值sessionStorage+false与 MSAL.js 自身的默认保持一致(源码注释中特别注明 “This matches the defaults in msal.js”)。二者组合意味着:令牌仅存活于当前浏览器标签页会话,关闭标签页即失效,安全性较高但每次新开标签页都需要重新认证;若希望跨标签页保持登录态,可将CacheLocation调整为"localStorage"。
默认值背后的执行逻辑:MsalDefaultOptionsConfiguration
虽然MsalProviderOptions在类型定义里就带了默认值,但还有一部分依赖运行时环境(如基地址)的默认值必须“后置处理”。这正是MsalDefaultOptionsConfiguration(MsalDefaultOptionsConfiguration.cs)的职责——它实现了IPostConfigureOptions<RemoteAuthenticationOptions<MsalProviderOptions>>,注入NavigationManager获取应用当前基地址。
其Configure方法共做四件事(对应 L20-L42):
// 1) 用户标识中的 scope 声明默认取 "scp"(Bearer 令牌格式) options.UserOptions.ScopeClaim ??= "scp"; // 2) 认证类型默认取 ClientId options.UserOptions.AuthenticationType ??= options.ProviderOptions.Authentication.ClientId; // 3) RedirectUri 为空或相对路径时,基于当前基地址解析为绝对 URI // 默认相对路径为 "authentication/login-callback" if (redirectUri == null || !Uri.TryCreate(redirectUri, UriKind.Absolute, out _)) { redirectUri ??= "authentication/login-callback"; options.ProviderOptions.Authentication.RedirectUri = _navigationManager.ToAbsoluteUri(redirectUri).AbsoluteUri; } // 4) PostLogoutRedirectUri 同理,默认 "authentication/logout-callback" options.ProviderOptions.Authentication.NavigateToLoginRequestUrl = false;由此可以回答几个常见的“为什么”:
- 为什么 appsettings.json 里写
RedirectUri: "authentication/login-callback"也能正常工作?因为该配置器发现它不是绝对 URI 后,会用NavigationManager.ToAbsoluteUri拼出完整的应用内地址,自动适配部署路径,无需手写完整域名; - 为什么
NavigateToLoginRequestUrl即使你配置为true也会被关掉?因为配置器在IPostConfigureOptions阶段无条件赋值false——需要说明,赋值发生在后置阶段,若你在configure回调中先配置了true,仍会被此覆盖(从当前源码看该行为是强制的); - ScopeClaim 为什么要默认
"scp"?微软签发访问令牌中的作用域声明名为scp,将其作为UserOptions.ScopeClaim的默认值,才能让下游IAccessTokenProvider/授权逻辑正确解析令牌内的作用域。
由于它继承自RemoteAuthenticationOptions<MsalProviderOptions>的认证状态基类,RedirectUri等参数要求是绝对地址或基地址相对地址,这也解释了为何配置文件里常见的写法是纯相对路径字符串——最终都会在这里被补全为NavigationManager解析出的绝对地址。同时注意Configure与PostConfigure两方法的配合:PostConfigure只在 name 为默认名(Options.DefaultName)时触发,确保多实例配置场景下默认值兜底只作用于默认命名实例。
组装进应用:Program.cs 中的完整接入形态
综合上述配置模型,在一个最小 Blazor WebAssembly 应用中接入 Azure AD 认证的完整代码如下(与此仓库项目模板 ComponentsWebAssembly-CSharp 模板 生成的结构一致):
using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.Authentication.WebAssembly.Msal; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<HeadOutlet>("head::after"); // 1) 注册 MSAL 认证,配置项从 appsettings.json 的 AzureAd 节读取 builder.Services.AddMsalAuthentication(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); // 可选:为下游 API 预取令牌 options.ProviderOptions.DefaultAccessTokenScopes.Add("api://{api-app-id}/access_as_user"); // 可选:如需静默令牌刷新以外的交互方式 // options.ProviderOptions.LoginMode = "redirect"; }); await builder.Build().RunAsync();配套的 UI 结构(来自模板源码)包括:
- 认证路由与回跳:在 Pages/Authentication.razor 中暴露
/authentication/{action}路由并渲染<RemoteAuthenticatorView Action="@Action" />,负责承接 login、login-callback、logout、logged-out 等动作; - 登录入口:由 Layout/LoginDisplay.razor 结合
AuthorizeView显示“登录/用户信息/登出”; - 未登录重定向:通过 Layout/RedirectToLogin.razor 在用户访问受保护页面时跳转登录;
- 路由级授权:在 App.razor 中以
CascadingAuthenticationState+AuthorizeRouteView包裹路由,并在<NotAuthorized>片段中触发上述重定向。
模板中Home.razor还带有一段“在 Program.cs 中配置身份提供商详情前,认证不会生效”的提示逻辑,与AddOidcAuthentication(OIDC 分支)并列存在——当工程选择微软账户作为身份源时,实际生成的就是AddMsalAuthentication分支,且Program.Main.cs中通过编译期条件(如IndividualLocalAuth)决定采用哪一套注册代码。
发布与裁剪注意事项
由于 Blazor WebAssembly 默认在发布时会对托管程序集做裁剪(trimming),而MsalProviderOptions、MsalCacheOptions、MsalAuthenticationOptions属于由 JSON 反序列化和 JS 互操作按名称反射访问的类型,必须防止被裁剪器移除。仓库为此提供了 ILLink.Descriptors.xml,以 XML 描述符的形式对这三个类型声明preserve="all":
<linker> <assembly fullname="Microsoft.Authentication.WebAssembly.Msal"> <type fullname="Microsoft.Authentication.WebAssembly.Msal.Models.MsalProviderOptions" preserve="all" /> <type fullname="Microsoft.Authentication.WebAssembly.Msal.Models.MsalCacheOptions" preserve="all" /> <type fullname="Microsoft.Authentication.WebAssembly.Msal.MsalAuthenticationOptions" preserve="all" /> </assembly> </linker>这从侧面印证:三个选项模型是序列化/互操作的关键契约。若你在自己的代码中扩展了自定义选项类型并希望其在裁剪后仍可用,同样需要以[DynamicallyAccessedMembers]标注或提供描述符——这也是 MsalWebAssemblyServiceCollectionExtensions.cs 中泛型参数带JsonSerialized约束的原因。
版本与配套基础包
该包依赖 Blazor 的远程认证基础设施与 MSAL.js 运行时,属于Microsoft.AspNetCore.App共享框架之外的独立 NuGet 包(当前仓库中发布产物对应版本请以实际安装时 NuGet 解析结果为准)。涉及同一类问题的邻近包还包括面向 OIDC(非微软身份源)的Microsoft.AspNetCore.Components.WebAssembly.Authentication系列。选择依据很简单:身份源是Azure AD / Azure AD B2C用本包;是其他 OIDC 提供方则使用通用的 OIDC 认证封装。
从调试角度,可观察浏览器 DevTools 中sessionStorage里以msal.开头的缓存键,以及登录/登出回调时页面在/authentication/login-callback与/authentication/logout-callback之间的跳转——这两处路由与缓存位置正是本文所述默认值(CacheLocation = "sessionStorage"、相对回调路径)在运行时的直接体现。
小结
Microsoft.Authentication.WebAssembly.Msal的接入路径可以概括为一条链路:
AddMsalAuthentication(options => ...)(MsalWebAssemblyServiceCollectionExtensions.cs)→ 注册通用远程认证 + 注册MsalDefaultOptionsConfiguration后置配置器 →MsalProviderOptions(含Authentication/Cache/作用域/登录模式)被规范化(相对回调地址补全为绝对地址、ScopeClaim="scp"、关闭NavigateToLoginRequestUrl)→ 通过 Interop/AuthenticationService.ts 驱动 MSAL.js 完成 popup/redirect 登录 → 配合模板中的RemoteAuthenticatorView、AuthorizeRouteView、LoginDisplay完成完整 UI 闭环。
掌握了安装命令、三大选项模型(MsalProviderOptions/MsalAuthenticationOptions/MsalCacheOptions)各自的默认值与适用场景,再理解了默认值后置配置器的工作原理,你就可以在 Blazor WebAssembly 应用中自如地接入 Azure AD / Azure AD B2C 登录,并能在登录模式、缓存位置、令牌作用域等关键点上做出符合业务需要的取舍。
【免费下载链接】aspnetcoreASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.项目地址: https://gitcode.com/GitHub_Trending/as/aspnetcore
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考