授权码流登录后如何返回原页面?
授权码流登录后重定向原页面的通用方案
针对你遇到的登录后重定向原页面的需求,业界有两种成熟的通用做法,完全可以解决你的问题:
一、用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
相关产品推荐
相关产品推荐

