Next-auth + RTK Query:请求返回401时如何跳转至登录页?
解决Next-auth + RTK Query 401未授权自动跳转登录页的方案
1. 全局捕获RTK Query的401错误
RTK Query支持自定义baseQuery来统一处理请求错误,这是捕获401的核心入口。你可以包装fetchBaseQuery,在错误回调里判断状态码并执行跳转:
// src/services/apiSlice.js import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; import { signOut } from 'next-auth/react'; const customBaseQuery = fetchBaseQuery({ baseUrl: '/api', credentials: 'include', // 确保携带会话cookie prepareHeaders: (headers) => { // 从Next-auth会话中获取token并添加到请求头 const session = JSON.parse(localStorage.getItem('next-auth.session')); if (session?.user?.token) { headers.set('authorization', `Bearer ${session.user.token}`); } return headers; }, }); const baseQueryWithReauth = async (args, api, extraOptions) => { let result = await customBaseQuery(args, api, extraOptions); // 捕获401错误并处理 if (result.error?.status === 401) { // 先登出Next-auth会话 await signOut({ redirect: false }); // 跳转到登录页,携带当前页面作为回调地址 window.location.href = `/api/auth/signin?callbackUrl=${encodeURIComponent(window.location.href)}`; } return result; }; export const apiSlice = createApi({ reducerPath: 'api', baseQuery: baseQueryWithReauth, endpoints: (builder) => ({ // 你的业务接口定义 }), });
2. 在_app.js中监听会话状态变化
除了请求层面的401捕获,还要监听Next-auth的会话状态,当会话主动失效时直接跳转:
// pages/_app.js import { useSession } from 'next-auth/react'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; function MyApp({ Component, pageProps }) { const { status } = useSession(); const router = useRouter(); useEffect(() => { // 当会话变为未认证状态,且当前不在登录页时跳转 if (status === 'unauthenticated' && router.pathname !== '/api/auth/signin') { router.push('/api/auth/signin'); } }, [status, router]); return <Component {...pageProps} />; } export default MyApp;
3. 优化Next-auth的JWT配置(针对JWT认证场景)
你之前设置的maxAge:60是会话有效期,但还需要在jwt回调里处理token刷新,减少不必要的401:
// pages/api/auth/[...nextauth].js import NextAuth from 'next-auth'; import CredentialsProvider from 'next-auth/providers/credentials'; export default NextAuth({ providers: [ CredentialsProvider({ async authorize(credentials) { // 这里写你的登录验证逻辑,返回包含token和过期时间的用户对象 const user = await yourAuthService.login(credentials); return user; }, }), ], session: { strategy: 'jwt', maxAge: 60, // 会话有效期60秒 }, jwt: { async jwt({ token, user }) { // 首次登录时保存token信息 if (user) { token.accessToken = user.accessToken; token.expiresAt = user.expiresAt; token.refreshToken = user.refreshToken; } // 提前1分钟检查token是否即将过期,触发刷新 if (Date.now() < token.expiresAt - 60000) { return token; } // 执行token刷新逻辑 try { const refreshedData = await yourAuthService.refreshToken(token.refreshToken); return { ...token, accessToken: refreshedData.accessToken, expiresAt: refreshedData.expiresAt, }; } catch (err) { // 刷新失败,标记token错误,触发会话失效 return { ...token, error: 'RefreshTokenFailed' }; } }, async session({ session, token }) { // 将token信息同步到会话对象 session.user.token = token.accessToken; session.error = token.error; return session; }, }, });
为什么之前的方案无效?
你设置的refetchInterval和refetchOnWindowFocus只是触发数据重新请求,但没有处理请求返回的401错误,所以即使请求失败也不会触发跳转。必须通过全局错误捕获+会话状态监听的组合,才能覆盖所有401场景(包括token过期、后端主动返回401、会话主动失效)。
内容的提问来源于stack exchange,提问作者LeoVenezia
相关产品推荐
相关产品推荐

