You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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库在服务端实现授权码流程:
    1. 在/api/auth/login路由中,生成Azure AD登录URL并跳转用户到该地址
    2. 在/api/auth/callback路由中,接收授权码,调用Azure AD令牌端点获取access_token和id_token
    3. 将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 06:45:37