NextJS+Next-Auth结合Service Worker:获取原始JWT令牌问题
解决方案:NextJS+Next-Auth结合Service Worker处理API授权头问题
一、全局注册Service Worker
无需在每个页面单独添加注册代码,直接在_app.tsx(或_app.js)中全局处理——这是所有页面的统一入口,能确保无论从哪个页面启动应用,Service Worker都会被注册:
// pages/_app.tsx import { useEffect } from 'react'; import { SessionProvider } from 'next-auth/react'; function MyApp({ Component, pageProps }) { useEffect(() => { if ('serviceWorker' in navigator) { window.addEventListener('load', async () => { try { await navigator.serviceWorker.register('/sw.js'); } catch (err) { console.error('Service Worker注册失败:', err); } }); } }, []); return ( <SessionProvider session={pageProps.session}> <Component {...pageProps} /> </SessionProvider> ); } export default MyApp;
二、让Service Worker自动获取JWT令牌
方案1:扩展Next-Auth API端点(完全可行)
在pages/api/auth/下新增自定义路由,让Service Worker直接调用接口获取JWT,无需页面层传递:
// pages/api/auth/get-jwt.ts import { getToken } from 'next-auth/jwt'; import type { NextApiRequest, NextApiResponse } from 'next'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); if (!token) return res.status(401).json({ error: '未授权' }); // 假设你的JWT存储在token的accessToken字段中,根据实际调整 res.status(200).json({ jwt: token.accessToken }); }
然后在Service Worker中启动时调用该接口,后续监听fetch事件自动添加授权头:
// public/sw.js let jwtToken = null; // 初始化获取JWT async function fetchJwt() { try { const res = await fetch('/api/auth/get-jwt'); if (res.ok) { const data = await res.json(); jwtToken = data.jwt; } } catch (err) { console.error('获取JWT失败:', err); } } // 安装阶段触发初始化 self.addEventListener('install', (event) => { event.waitUntil(fetchJwt()); }); // 监听fetch请求添加授权头 self.addEventListener('fetch', (event) => { // 仅针对目标外部API添加头 if (event.request.url.startsWith('https://你的外部API域名/')) { if (!jwtToken) { // 无token时先重新获取再发起请求 event.respondWith( fetchJwt().then(() => { const modifiedReq = new Request(event.request, { headers: new Headers({ ...event.request.headers, 'Authorization': `Bearer ${jwtToken}` }) }); return fetch(modifiedReq); }) ); } else { const modifiedReq = new Request(event.request, { headers: new Headers({ ...event.request.headers, 'Authorization': `Bearer ${jwtToken}` }) }); event.respondWith(fetch(modifiedReq)); } } });
方案2:全局监听会话变化推送JWT
如果不想扩展API,可在_app.tsx中监听Next-Auth会话更新,自动给Service Worker发送JWT:
// pages/_app.tsx import { useEffect } from 'react'; import { SessionProvider, useSession } from 'next-auth/react'; function SessionSync() { const { data: session } = useSession(); useEffect(() => { if (session?.user?.accessToken && navigator.serviceWorker.controller) { navigator.serviceWorker.controller.postMessage({ type: 'UPDATE_JWT', token: session.user.accessToken }); } }, [session]); return null; } function MyApp({ Component, pageProps }) { // ... 之前的Service Worker注册代码 return ( <SessionProvider session={pageProps.session}> <SessionSync /> <Component {...pageProps} /> </SessionProvider> ); } export default MyApp;
Service Worker中监听消息存储token:
// public/sw.js let jwtToken = null; self.addEventListener('message', (event) => { if (event.data.type === 'UPDATE_JWT') { jwtToken = event.data.token; } }); // fetch事件处理同上
三、阻止Next-Auth在会话中附加JWT
修改Next-Auth的session()回调,只返回必要的用户信息,移除JWT的前端传递:
// pages/api/auth/[...nextauth].ts import NextAuth from 'next-auth'; import Providers from 'next-auth/providers'; export default NextAuth({ providers: [ // 你的OAuth配置 ], callbacks: { async jwt({ token, user, account }) { // 这里依然保留JWT的捕获逻辑,用于后端和自定义API接口 if (account) token.accessToken = account.access_token; return token; }, async session({ session, token }) { // 仅返回用户基本信息,不传递JWT到前端会话 session.user.id = token.id; session.user.name = token.name; session.user.email = token.email; return session; } }, secret: process.env.NEXTAUTH_SECRET });
总结
- 全局注册Service Worker用
_app.tsx即可覆盖所有页面入口; - 扩展Next-Auth API端点是最简洁的令牌获取方案,无需页面层干预;
- 修改
session()回调可彻底避免JWT在前端会话中冗余传递。
内容的提问来源于stack exchange,提问作者Zoltan Hernyak
相关产品推荐
相关产品推荐

