Next.js中如何在服务器端预渲染受Azure AD保护的API?
基于Next.js + Azure AD实现预渲染受保护页面
核心思路
利用Next.js的服务端能力(API路由、中间件、getServerSideProps/getStaticProps)在服务端完成Azure AD认证令牌的获取与验证,从而在预渲染阶段就能调用受保护接口,实现页面即时加载。
步骤1:配置Azure AD服务端认证流程
- 在Azure AD注册应用,设置重定向URI为你的Next.js项目的API路由(如
/api/auth/callback),并启用"授权码流" - 使用
@azure/msal-node库在服务端实现授权码流程:- 在
/api/auth/login路由中,生成Azure AD登录URL并跳转用户到该地址 - 在
/api/auth/callback路由中,接收授权码,调用Azure AD令牌端点获取access_token和id_token - 将
access_token(设为HttpOnly、Secure)和用户基本信息存入Cookie,确保前端无法直接篡改
- 在
示例代码(/api/auth/callback):
import { ConfidentialClientApplication } from '@azure/msal-node'; const config = { auth: { clientId: process.env.AZURE_AD_CLIENT_ID, authority: `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}`, clientSecret: process.env.AZURE_AD_CLIENT_SECRET, }, }; const cca = new ConfidentialClientApplication(config); export default async function handler(req, res) { const { code } = req.query; const redirectUri = `${process.env.NEXT_PUBLIC_BASE_URL}/api/auth/callback`; const tokenRequest = { code, redirectUri, scopes: ['https://your-api-scope/.default'], }; try { const response = await cca.acquireTokenByCode(tokenRequest); // 存入HttpOnly Cookie res.setHeader('Set-Cookie', [ `access_token=${response.accessToken}; HttpOnly; Secure; Path=/; SameSite=Lax`, `user=${JSON.stringify(response.account)}; Path=/; SameSite=Lax`, ]); res.redirect(req.query.state || '/'); } catch (error) { res.status(401).json({ error: '认证失败' }); } }
步骤2:服务端预渲染(SSR)获取受保护数据
在需要预渲染的受保护页面中,使用getServerSideProps从Cookie中读取令牌,调用接口并传递数据给组件:
export async function getServerSideProps(context) { const { req } = context; const accessToken = req.cookies.access_token; // 检查令牌是否存在且有效 if (!accessToken) { return { redirect: { destination: '/api/auth/login?state=' + context.resolvedUrl, permanent: false, }, }; } // 调用受保护API const apiResponse = await fetch('https://your-protected-api/data', { headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!apiResponse.ok) { // 令牌失效,重定向到登录页 return { redirect: { destination: '/api/auth/login?state=' + context.resolvedUrl, permanent: false, }, }; } const data = await apiResponse.json(); return { props: { data } }; } export default function ProtectedPage({ data }) { return <div>{/* 渲染预加载的数据 */}</div>; }
步骤3:全局访问控制(Next.js Middleware)
创建middleware.js文件,全局拦截非首页的请求,强制认证:
import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 首页无需认证,直接放行 if (pathname === '/') { return NextResponse.next(); } // 检查Cookie中的令牌 const accessToken = request.cookies.get('access_token')?.value; if (!accessToken) { // 重定向到登录页,携带当前页面地址作为state const loginUrl = new URL('/api/auth/login', request.url); loginUrl.searchParams.set('state', pathname); return NextResponse.redirect(loginUrl); } // 令牌存在,放行 return NextResponse.next(); } export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], };
步骤4:静态生成(SSG/ISR)适配(可选)
如果部分受保护页面不需要实时数据,可以使用增量静态再生(ISR),通过Azure AD的客户端凭证流获取服务端令牌,在构建/再生时获取数据:
export async function getStaticProps() { // 使用客户端凭证流获取服务端令牌 const tokenResponse = await fetch(`https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/oauth2/v2.0/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: process.env.AZURE_AD_CLIENT_ID, client_secret: process.env.AZURE_AD_CLIENT_SECRET, scope: 'https://your-api-scope/.default', grant_type: 'client_credentials', }), }); const { access_token } = await tokenResponse.json(); const apiData = await fetch('https://your-protected-api/static-data', { headers: { Authorization: `Bearer ${access_token}` }, }); const data = await apiData.json(); return { props: { data }, revalidate: 3600, // 每小时再生一次 }; }
注意事项
- 确保Cookie设置
HttpOnly和Secure(生产环境),防止XSS攻击 - 定期验证令牌的有效性,避免使用过期令牌调用API
- 客户端凭证流仅适用于无需用户上下文的场景,用户专属数据仍需用授权码流获取的令牌
内容的提问来源于stack exchange,提问作者keshav
相关产品推荐
相关产品推荐

