NextAuth集成Okta SSO出现无限重定向循环问题求助
NextAuth 无限重定向循环问题排查与简化配置
最近把应用主页面改为默认首页(重命名为index.tsx)后,出现了NextAuth无限重定向死循环。应用无独立登录页,通过Okta SSO完成登录。尝试过创建signin.tsx组件、在pages/index的getServerSideProps里加路由保护重定向,但即使移除后者,循环依然存在。希望简化配置,找出冗余部分。
[...NEXTAUTH] 配置
export const authOptionsFunction = (): AuthOptions => { return { providers: [ Okta({ //@ts-ignore clientId: process.env.OKTA_OAUTH2_CLIENT_ID as string, //@ts-ignore clientSecret: Config.OKTA_OAUTH2_CLIENT_SECRET as string, //@ts-ignore issuer: process.env.OKTA_OAUTH2_ISSUER as string, // clientId: process.env.OKTA_OAUTH2_CLIENT_ID as string, // clientSecret: process.env.OKTA_OAUTH2_CLIENT_SECRET as string, // issuer: process.env.OKTA_OAUTH2_ISSUER as string, }) ], //@ts-ignore secret: process.env.NEXTAUTH_SECRET, //secret: process.env.NEXTAUTH_SECRET as string, session: { strategy: 'jwt'}, callbacks: { async jwt({ token, account }: any) { if (account) { token.accessToken = account.access_token; token.idToken = account.id_token; token.oktaId = account.providerAccountId } var tokenParsed = JSON.parse(Buffer.from(token.accessToken.split('.')[1], 'base64').toString()); const dateNowInSeconds = new Date().getTime() / 1000 if (dateNowInSeconds > tokenParsed.exp) { throw Error("expired Okta access token"); } return token; }, async session({ session, token }: any) { // get our native current user data here const res = await GET_CURRENT_USER(token); const { data } = await res.json(); session.accessToken = token.accessToken; session.idToken = token.idToken; session.oktaId = token.oktaId; session.currentUser = data; return session; }, async signIn({ user, account, profile, email, credentials}) { return true; }, async redirect({ url, baseUrl }) { // Allows relative callback URLs if (url.startsWith("/")) return `${baseUrl}${url}` // Allows callback URLs on the same origin else if (new URL(url).origin === baseUrl) return url return baseUrl } }, debug: process.env.NODE_ENV === 'development', pages: { signIn: "/auth/signin", }, } } export const authOptions: NextAuthOptions = authOptionsFunction() export default NextAuth(authOptions);
Middleware 配置
export default withAuth( (req: NextRequest & { nextauth: { token: JWT | null } }) => { if (req.nextUrl.pathname.startsWith('/auth') || req.nextUrl.pathname.startsWith('/_next')) { return NextResponse.next(); } // if there is no session if (!req.nextauth.token) { req.nextUrl.pathname = '/auth/signin'; return NextResponse.redirect(req.nextUrl); } return NextResponse.next(); }, { callbacks: { authorized() { return true }, }, pages: { signIn: "/auth/signin", }, } );
SignIn 组件
const SignIn = () => { const { status } = useSession(); const router = useRouter(); React.useEffect(() => { if (status === 'unauthenticated') { signIn('okta'); } else if (status === 'authenticated') { router.push('/'); } }, [status]); return ( <> </> ) } export default SignIn;
Pages/Index 的 getServerSideProps 片段
if (!session) { return { redirect: { destination: '/auth/signin', permanent: false, } } }
问题根源与简化方案
重复路由保护触发循环:Middleware已全局拦截未认证请求跳转到
/auth/signin,同时pages/index的getServerSideProps又做了一次重定向,加上SignIn组件认证后跳回/,三者叠加引发循环。冗余配置清理:
- 移除
pages/index中getServerSideProps的重定向逻辑,统一由Middleware处理路由保护。 - 简化SignIn组件:无需手动判断未认证状态调用
signIn,NextAuth会自动处理跳转;仅保留认证后返回首页的逻辑即可。 - 去掉
authOptionsFunction的冗余包装,直接定义authOptions。 - 删除
jwt回调中的手动token过期检查:NextAuth会自动处理JWT过期,手动抛出错误可能导致流程中断触发循环。
- 移除
修正后的关键代码示例:
- 简化版SignIn组件:
import { useSession, signIn } from "next-auth/react"; import { useRouter } from "next/router"; import React from "react"; const SignIn = () => { const { status } = useSession(); const router = useRouter(); React.useEffect(() => { if (status === "authenticated") { router.push("/"); } }, [status]); if (status === "unauthenticated") { signIn("okta"); } return <div>正在跳转至登录页面...</div>; }; export default SignIn; - 简化版[...NEXTAUTH].ts:
import NextAuth, { AuthOptions } from "next-auth"; import Okta from "next-auth/providers/okta"; import Config from "../path/to/config"; export const authOptions: AuthOptions = { providers: [ Okta({ clientId: process.env.OKTA_OAUTH2_CLIENT_ID as string, clientSecret: Config.OKTA_OAUTH2_CLIENT_SECRET as string, issuer: process.env.OKTA_OAUTH2_ISSUER as string, }), ], secret: process.env.NEXTAUTH_SECRET as string, session: { strategy: "jwt" }, callbacks: { async jwt({ token, account }) { if (account) { token.accessToken = account.access_token; token.idToken = account.id_token; token.oktaId = account.providerAccountId; } return token; }, async session({ session, token }) { const res = await fetch("/api/current-user", { headers: { Authorization: `Bearer ${token.accessToken}` }, }); const { data } = await res.json(); session.accessToken = token.accessToken; session.idToken = token.idToken; session.oktaId = token.oktaId; session.currentUser = data; return session; }, async redirect({ url, baseUrl }) { if (url.startsWith("/")) return `${baseUrl}${url}`; if (new URL(url).origin === baseUrl) return url; return baseUrl; }, }, debug: process.env.NODE_ENV === "development", pages: { signIn: "/auth/signin", }, }; export default NextAuth(authOptions); - 修正后的Middleware:
import { withAuth } from "next-auth/middleware"; import { NextResponse } from "next/server"; export default withAuth( (req) => { if (req.nextUrl.pathname.startsWith("/auth") || req.nextUrl.pathname.startsWith("/_next")) { return NextResponse.next(); } }, { callbacks: { authorized: ({ token }) => !!token, }, pages: { signIn: "/auth/signin", }, } );
- 简化版SignIn组件:
内容的提问来源于stack exchange,提问作者12Noone
相关产品推荐
相关产品推荐

