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

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,
        }
    }
}

问题根源与简化方案

  1. 重复路由保护触发循环:Middleware已全局拦截未认证请求跳转到/auth/signin,同时pages/index的getServerSideProps又做了一次重定向,加上SignIn组件认证后跳回/,三者叠加引发循环。

  2. 冗余配置清理:

    • 移除pages/index中getServerSideProps的重定向逻辑,统一由Middleware处理路由保护。
    • 简化SignIn组件:无需手动判断未认证状态调用signIn,NextAuth会自动处理跳转;仅保留认证后返回首页的逻辑即可。
    • 去掉authOptionsFunction的冗余包装,直接定义authOptions。
    • 删除jwt回调中的手动token过期检查:NextAuth会自动处理JWT过期,手动抛出错误可能导致流程中断触发循环。
  3. 修正后的关键代码示例:

    • 简化版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",
          },
        }
      );
      

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 01:09:54