如何用 MSAL 在 Refine 中接入 Azure AD B2C 登录?

发布时间:2026/9/12 16:18:31
如何用 MSAL 在 Refine 中接入 Azure AD B2C 登录? 如何用 MSAL 在 Refine 中接入 Azure AD B2C 登录【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine假设你已经在用 Refine 做了一个内部管理应用现在不想再维护自己的账号密码而是让企业用户直接用 Azure AD B2C 的社交账号、企业账号或本地账号登录。目标结果是应用打开时先经过一个登录页点击按钮跳转到 Azure B2C 登录页登录成功后应用保存访问令牌并自动把它附加到后续的数据请求上。Refine 官方的 Azure AD 文档 走的就是这条路用 Microsoft Authentication LibraryMSAL的 JavaScript 版本azure/msal-browser加 React 辅助包azure/msal-react完成认证再由 Refine 的authProvider接管登录态判断。文档以 Azure AD B2C 为例并说明用普通 Azure AD 做认证的步骤与之非常相似。下面按文档给出的顺序逐步接入。代码示例基于 antd UI 集成refinedev/antd和refinedev/react-router路由。安装 MSAL 依赖在 Refine 项目根目录安装两个包三个包管理器任选其一# npm npm i azure/msal-browser azure/msal-react # pnpm pnpm add azure/msal-browser azure/msal-react # yarn yarn add azure/msal-browser azure/msal-react这两个包分别提供 MSAL 的浏览器端实现PublicClientApplication、事件回调等和 React 集成MsalProvider、useMsal、useIsAuthenticated等 hook。创建 MSAL 配置文件在src/config.ts中创建 MSAL 配置。文档给出的完整内容如下其中带YOUR_前缀的字符串是占位符需要替换为你自己在 Azure 租户中申请到的值每个占位符对应的环境变量写法文档已用注释保留在代码里按注释中的写法接入自己的环境变量即可import { Configuration, LogLevel } from azure/msal-browser; export const msalConfig: Configuration { auth: { clientId: YOUR_CLIENT_ID, //${process.env.REACT_APP_AZURE_AAD_CLIENT_ID}, authority: YOUR_AUTHORITY, //https://${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com/${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.onmicrosoft.com/${process.env.REACT_APP_AZURE_AAD_POLICY_NAME}, knownAuthorities: [YOUR_KNOWN_AUTHORITIES], //[${process.env.REACT_APP_AZURE_AAD_TENANT_NAME}.b2clogin.com], redirectUri: http://localhost:3000/, // Replace appropriately postLogoutRedirectUri: window.location.origin, }, cache: { cacheLocation: sessionStorage, // This configures where your cache will be stored storeAuthStateInCookie: false, // Set this to true if you are having issues on IE11 or Edge }, }; // Add scopes here for ID token to be used at Microsoft identity platform endpoints. export const loginRequest { scopes: [User.Read] }; export const tokenRequest { scopes: [...] // Replace ... with your custom scopes }; // Add the endpoints here for Microsoft Graph API services youd like to use. export const graphConfig { graphMeEndpoint: ENTER_THE_GRAPH_ENDPOINT_HERE/v1.0/me };几个需要替换或确认的位置clientId、authority、knownAuthorities来自你的 Azure B2C 租户与客户端应用注册。按代码注释中的环境变量形式拼接后authority形如https://tenant.b2clogin.com/tenant.onmicrosoft.com/policyknownAuthorities为tenant.b2clogin.com。redirectUri登录完成后跳回的地址文档示例为http://localhost:3000/注释要求按你的实际部署地址替换Replace appropriately。tokenRequest.scopes代码里的...是占位文档注释明确要求替换成你需要的自定义 scopes。graphConfig只有你要调用 Microsoft Graph API 时才需要按注释填入实际的 Graph 端点即可不需要 Graph 可以忽略这一节。文档建议用环境变量管理这些配置参数而不是把值直接写死在代码里。用 MsalProvider 包裹根组件修改src/index.tsx创建PublicClientApplication实例监听LOGIN_SUCCESS事件——登录成功后先静默获取访问令牌成功后写入localStorage静默获取失败时回退到弹窗方式获取。同时用MsalProvider包裹整个应用import React from react; import ReactDOM from react-dom/client; import { EventType, PublicClientApplication, AccountInfo, EventPayload, SilentRequest, } from azure/msal-browser; import { MsalProvider } from azure/msal-react; import App, { TOKEN_KEY } from ./App; import { msalConfig, tokenRequest } from ./config; const msalInstance new PublicClientApplication(msalConfig); msalInstance.addEventCallback(async (event) { if (event.eventType EventType.LOGIN_SUCCESS) { const payload: EventPayload event.payload; msalInstance.setActiveAccount(payload as AccountInfo); let account msalInstance.getActiveAccount(); const request: SilentRequest { ...tokenRequest, account: account!, }; try { // Silently acquires an access token which is then attached to a request for API access const response await msalInstance.acquireTokenSilent(request); console.log(Fetching access token: success); console.log(Scopes, response.scopes); console.log(Token Type, response.tokenType); localStorage.setItem(TOKEN_KEY, response.accessToken); } catch (e) { msalInstance.acquireTokenPopup(request).then((response) { localStorage.setItem(TOKEN_KEY, response.accessToken); }); } } }); const root ReactDOM.createRoot( document.getElementById(root) as HTMLElement, ); root.render( React.StrictMode MsalProvider instance{msalInstance} App / /MsalProvider /React.StrictMode, );这里TOKEN_KEY是从App.tsx导入的常量取值refine-auth后面保存令牌、读取令牌都通过它进行。覆盖 Refine 的登录页Refine 允许覆盖默认登录页。在src/下创建login.tsx用一个按钮触发useLogin()实际跳转由authProvider.login里的loginRedirect()完成import React from react; import { useLogin } from refinedev/core; import { Layout, Button } from antd; const LoginPage () { const SignInButton () { const { mutate: login } useLogin(); return ( Button typeprimary sizelarge block onClick{() login()} Sign in /Button ); }; return ( Layout style{{ background: radial-gradient(50% 50% at 50% 50%, #63386A 0%, #310438 100%), backgroundSize: cover, }} div style{{ height: 100vh, display: flex }} div style{{ maxWidth: 200px, margin: auto }} div style{{ marginBottom: 28px }} img src./refine.svg altRefine / /div SignInButton / /div /div /Layout ); }; export default LoginPage;编写 authProvider 与请求令牌注入核心在src/App.tsx定义AuthProvider让 Refine 的登录态完全由 MSAL 的账号和令牌驱动同时给 axios 实例加请求拦截器把localStorage中的令牌写进Authorization请求头import { Refine, AuthProvider, Authenticated } from refinedev/core; import { Layout, ErrorComponent } from refinedev/antd; import routerProvider, { NavigateToResource, CatchAllNavigate, } from refinedev/react-router; import dataProvider from refinedev/simple-rest; import { useIsAuthenticated, useMsal } from azure/msal-react; import { AccountInfo, SilentRequest } from azure/msal-browser; import axios from axios; import { BrowserRouter, Routes, Route, Outlet } from react-router; import LoginPage from ./login; import { tokenRequest } from ./config; export const TOKEN_KEY refine-auth; export const axiosInstance axios.create(); axiosInstance.interceptors.request.use( // Here we can perform any function wed like on the request (config) { // Retrieve the token from local storage const token localStorage.getItem(TOKEN_KEY); // Check if the header property exists if (config.headers) { // Set the Authorization header if it exists config.headers[Authorization] Bearer ${token}; } return config; }, ); const App: React.FC () { const API_URL https://api.fake-rest.refine.dev; const isAuthenticated useIsAuthenticated(); const { instance, inProgress, accounts } useMsal(); if (inProgress login || inProgress handleRedirect) { return divLoading.../div; } const account: AccountInfo accounts[0]; const request: SilentRequest { ...tokenRequest, account, }; const authProvider: AuthProvider { login: async () { instance.loginRedirect(); // Pick the strategy you prefer i.e. redirect or popup return { success: true, }; }, register: async () ({ success: true, }), resetPassword: async () ({ success: true, }), updatePassword: async () ({ success: true, }), logout: async () ({ success: true, }), check: async () { try { if (account) { const token await instance.acquireTokenSilent(request); localStorage.setItem(TOKEN_KEY, token.accessToken); return { authenticated: true, }; } else { return { authenticated: false, redirectTo: /login, }; } } catch (e) { return { authenticated: false, redirectTo: /login, }; } }, onError: async (error) { console.error(error); return { error }; }, getPermissions: async () null, getIdentity: async (): PromiseAccountInfo { if (account null || account undefined) { return null; } return account; }, }; return ( BrowserRouter Refine routerProvider{routerProvider} dataProvider{dataProvider(API_URL, axiosInstance)} authProvider{authProvider} resources{[ { name: posts, list: /posts, }, ]} Routes Route element{ Authenticated fallback{CatchAllNavigate to/login /} Layout Outlet / /Layout /Authenticated } Route path/posts element{divdummy list page/div} / /Route Route element{ Authenticated fallback{Outlet /} NavigateToResource / /Authenticated } Route path/login element{LoginPage /} / /Route Route path* element{ErrorComponent /} / /Routes /Refine /BrowserRouter ); }; export default App;各部分的作用login调用instance.loginRedirect()把浏览器跳转到 Azure B2C 登录页。注释说明也可以换成你偏好的策略redirect 或 popup。check这是登录态判断的核心。存在账号时静默获取令牌并写入localStorage返回authenticated: true没有账号或获取失败时返回authenticated: false并携带redirectTo: /login由Authenticated组件负责跳转。getIdentity直接返回当前AccountInfo供 UI 展示当前用户。路由结构受保护页面套在Authenticated里未登录时回退到/login/login页本身又包了一层Authenticated已登录用户访问它时会被NavigateToResource带回业务页。数据请求走dataProvider(API_URL, axiosInstance)文档示例中API_URL是https://api.fake-rest.refine.dev接入自己的后端时替换为你的 API 地址即可axiosInstance的拦截器保证每个请求自动带上Bearer令牌。register、resetPassword、updatePassword、logout在示例中统一返回success: true因为账号生命周期由 Azure B2C 侧管理应用内不做这些操作。验证接入是否生效文档代码中体现的验证点如下按流程核对应用启动时若 MSAL 正在执行login或handleRedirect页面显示Loading...说明重定向流程在走。未登录状态访问业务页面check返回未认证页面落到/login并出现 Sign in 按钮。点击 Sign in 后被重定向到 Azure B2C 登录页登录成功后LOGIN_SUCCESS回调触发静默获取令牌。文档示例在成功后打印的日志文档示例输出Fetching access token: success Scopes ... Token Type ...令牌以refine-auth为键存入localStorage后续经axiosInstance发出的请求Authorization头为Bearer token。已登录状态刷新页面check能静默拿到令牌并返回authenticated: true应用直接进入业务页而不是/login。限制与注意点示例的登录页和 App 基于 antdrefinedev/antd的Layout、ErrorComponent与refinedev/react-router使用其他 UI 包或路由集成时对应导入需要按各自的包调整MSAL 相关逻辑不变。文档以 Azure AD B2C 为例并说明认证普通 Azure AD 的步骤与之非常相似。redirectUri必须与浏览器中实际运行地址一致示例为本地开发地址http://localhost:3000/部署到别的环境时按代码注释替换。完整可核对的原始步骤见 documentation/docs/advanced-tutorials/auth/azure-ad.mdMSAL 各配置项的更多说明见 MSAL 官方文档。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考