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

授权码流登录后如何返回原页面?

授权码流登录后重定向原页面的通用方案

针对你遇到的登录后重定向原页面的需求,业界有两种成熟的通用做法,完全可以解决你的问题:

一、用state参数传递原URL(推荐)

虽然state最初设计是用于防CSRF,但实际开发中它就是用来携带登录上下文的标准方式,包括原页面URL。只要对state做安全处理(绑定CSRF随机值),就完全合规且安全。

登录接口修改(pages/api/auth/login.ts)

import { randomBytes } from 'crypto';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // 获取用户的原页面地址:可以是前端传入的returnTo参数,也可以用请求的referer
  const returnTo = req.query.returnTo || req.headers.referer || APP_URL;
  // 生成随机CSRF值,和原URL一起打包进state
  const csrf = randomBytes(16).toString('hex');
  const state = Buffer.from(JSON.stringify({ csrf, returnTo })).toString('base64');

  const authUrl = await myOpenIdClient.getAuthorizationUrl({
    prompt: 'login',
    redirect_uri: `${APP_URL}/api/auth/callback/token`,
    state // 把state传入授权URL
  });

  // 把CSRF值存到Cookie,用于回调时验证
  res.setHeader('Set-Cookie', `csrf=${csrf}; Path=/; HttpOnly; SameSite=Lax`);
  res.redirect(authUrl).end();
}

回调接口修改(pages/api/auth/callback/token.ts)

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const params = myOpenIdClient.callbackParams(req);
  
  // 解析state里的内容
  const state = JSON.parse(Buffer.from(params.state, 'base64').toString());
  // 验证CSRF值,防止恶意请求
  const cookieCsrf = req.cookies.csrf;
  if (!cookieCsrf || cookieCsrf !== state.csrf) {
    return res.status(403).end('无效的请求验证');
  }

  const tokenSet = await myOpenIdClient.callback(`${APP_URL}/api/auth/callback/token`, params);
  setTokenCookies(req, res, tokenSet.access_token, tokenSet.refresh_token);

  // 重定向到用户原来的页面
  res.redirect(state.returnTo || APP_URL).end();
}

二、用Cookie存储原URL

这种方式实现更简单,不需要在URL里暴露原地址,适合原URL较长或者不想公开的场景。

登录接口修改

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const returnTo = req.query.returnTo || req.headers.referer || APP_URL;
  // 把原URL编码后存到Cookie,加上安全属性
  res.setHeader('Set-Cookie', `returnTo=${encodeURIComponent(returnTo)}; Path=/; HttpOnly; SameSite=Lax`);

  const authUrl = await myOpenIdClient.getAuthorizationUrl({
    prompt: 'login',
    redirect_uri: `${APP_URL}/api/auth/callback/token`
  });
  res.redirect(authUrl).end();
}

回调接口修改

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const params = myOpenIdClient.callbackParams(req);
  const tokenSet = await myOpenIdClient.callback(`${APP_URL}/api/auth/callback/token`, params);
  setTokenCookies(req, res, tokenSet.access_token, tokenSet.refresh_token);

  // 读取Cookie里的原URL,默认跳转到主页
  const returnTo = decodeURIComponent(req.cookies.returnTo || APP_URL);
  // 用完后清除这个临时Cookie
  res.setHeader('Set-Cookie', `returnTo=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`);

  res.redirect(returnTo).end();
}

两种方案对比

  • state方案:URL会携带state参数,适合不需要隐藏原URL的场景,自带CSRF验证,安全性更高。
  • Cookie方案:URL更简洁,实现步骤少,适合原URL较长或敏感的场景,注意设置Cookie的安全属性即可。

内容的提问来源于stack exchange,提问作者NotX

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 20:42:44