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

Next.js 13中getServerSession返回null问题求助(Google OAuth)

Next.js 13 Google OAuth登录:getServerSession始终返回null的问题

问题描述

配置Next.js 13应用的Google OAuth登录时,跳转Google登录页流程正常,但登录后跳转到/dashboard时,getServerSession(authOptions)始终返回null,导致页面因会话检查返回404。已尝试设置NEXTAUTH_URL、NEXTAUTH_SECRET(含NEXTAUTH_URL_INTERNAL),问题仍存在。

环境变量配置

NEXTAUTH_SECRET=randomletters
NEXTAUTH_URL=http://localhost:3000

相关代码

(dashboard)/dashboard/page.tsx

import { FC } from "react"

import ApiDashboard from "@/components/ApiDashboard"
import RequestApiKey from "@/components/RequestApiKey"
import { db } from "@/lib/db"
import { getServerSession } from "next-auth"

import type { Metadata } from "next"
import { notFound } from "next/navigation"
import { authOptions } from "@/pages/api/auth/[...nextauth]"

export const metadata: Metadata = {
    title: "Similarity API | Dashboard",
    description: "Free and open-source text smilarity API",
}

const page = async () => {
    const user = await getServerSession(authOptions) //<-- 返回null
    if (!user) return notFound() //<-- 因此返回404

    const apiKey = await db.apiKey.findFirst({
        where: { userId: user.user.id, enabled: true },
    })
    return (
        <div className="mx-auto mt-16 max-w-7xl">
            {user ? <ApiDashboard /> : <RequestApiKey />}
        </div>
    )
}

export default page
import { getServerSession } from "next-auth/next"
import Link from "next/link"
import { FC } from "react"
import { buttonVariants } from "@/ui/Button"
import SignInButton from "@/components/ui/SignInButton"
import SignOutButton from "@/components/ui/SignOutButton"
import ThemeToggle from "./ThemeToggle"
import { authOptions } from "@/pages/api/auth/[...nextauth]"

const Navbar = async () => {
    const session = await getServerSession(authOptions) //<-- 同样返回null
    return ( /* 组件内容 */ ) 
}

[...nextauth].ts代码

import NextAuth from "next-auth"

import { db } from "@/lib/db"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"

function getGoogleCredentials(): { clientId: string; clientSecret: string } {
    const clientId = process.env.GOOGLE_CLIENT_ID
    const clientSecret = process.env.GOOGLE_CLIENT_SECRET
    if (!clientId || clientId.length === 0) {
        throw new Error("Missing GOOGLE_CLIENT_ID")
    }

    if (!clientSecret || clientSecret.length === 0) {
        throw new Error("Missing GOOGLE_CLIENT_SECRET")
    }

    return { clientId, clientSecret }
}

export const authOptions: NextAuthOptions = {
    adapter: PrismaAdapter(db),
    session: {
        strategy: "jwt",
    },
    pages: {
        signIn: "/login",
    },
    providers: [
        GoogleProvider({
            clientId: getGoogleCredentials().clientId,
            clientSecret: getGoogleCredentials().clientSecret,
        }),
    ],
    callbacks: {
        async session({ token, session }) {
            if (token) {
                session.user.id = token.id
                session.user.name = token.name
                session.user.email = token.email
                session.user.image = token.picture
            }

            return session
        },
        async jwt({ token, user }) {
            const dbUser = await db.user.findFirst({
                where: {
                    email: token.email,
                },
            })

            if (!dbUser) {
                token.id = user!.id
                return token
            }

            return {
                id: dbUser.id,
                name: dbUser.name,
                email: dbUser.email,
                picture: dbUser.image,
            }
        },
        async redirect() {
            return "/dashboard"
        },
    },
}

export default NextAuth(authOptions)

解决方案

1. 确认NextAuth版本兼容性

确保使用next-auth v4.20.0及以上版本,该版本完善了对Next.js 13 App Router的支持。升级命令:

npm update next-auth
# 或
yarn upgrade next-auth

2. 抽离authOptions到独立文件

将authOptions从pages/api/auth/[...nextauth]中抽离到@/lib/auth.ts,避免跨目录导入导致的上下文问题:

// @/lib/auth.ts
import { db } from "@/lib/db"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { NextAuthOptions } from "next-auth"
import GoogleProvider from "next-auth/providers/google"

function getGoogleCredentials(): { clientId: string; clientSecret: string } {
    const clientId = process.env.GOOGLE_CLIENT_ID
    const clientSecret = process.env.GOOGLE_CLIENT_SECRET
    if (!clientId || clientId.length === 0) {
        throw new Error("Missing GOOGLE_CLIENT_ID")
    }

    if (!clientSecret || clientSecret.length === 0) {
        throw new Error("Missing GOOGLE_CLIENT_SECRET")
    }

    return { clientId, clientSecret }
}

export const authOptions: NextAuthOptions = {
    adapter: PrismaAdapter(db),
    session: {
        strategy: "jwt",
    },
    pages: {
        signIn: "/login",
    },
    providers: [
        GoogleProvider({
            clientId: getGoogleCredentials().clientId,
            clientSecret: getGoogleCredentials().clientSecret,
        }),
    ],
    callbacks: {
        async session({ token, session }) {
            if (token) {
                session.user.id = token.id
                session.user.name = token.name
                session.user.email = token.email
                session.user.image = token.picture
            }
            return session
        },
        async jwt({ token, user }) {
            if (user) token.id = user.id
            const dbUser = await db.user.findUnique({
                where: { email: token.email },
            })
            if (!dbUser) return token
            return {
                id: dbUser.id,
                name: dbUser.name,
                email: dbUser.email,
                picture: dbUser.image,
            }
        },
        async redirect() {
            return "/dashboard"
        },
    },
    cookies: {
        sessionToken: {
            name: `next-auth.session-token`,
            options: {
                httpOnly: true,
                sameSite: "lax",
                path: "/",
                secure: process.env.NODE_ENV === "production",
            },
        },
    },
}

然后在pages/api/auth/[...nextauth].ts和所有App Router组件中,统一从@/lib/auth导入authOptions。

3. 修正Cookie配置

在authOptions中添加Cookie配置,确保本地开发时不强制HTTPS,避免Cookie无法写入:

// 已包含在上面的@/lib/auth.ts中
cookies: {
    sessionToken: {
        name: `next-auth.session-token`,
        options: {
            httpOnly: true,
            sameSite: "lax",
            path: "/",
            secure: process.env.NODE_ENV === "production", // 仅生产环境启用HTTPS
        },
    },
},

4. 优化JWT回调逻辑

改用findUnique查询用户(比findFirst更可靠),并优先处理首次登录的user参数:

// 已包含在上面的@/lib/auth.ts中
async jwt({ token, user }) {
    // 首次登录时直接设置token.id
    if (user) token.id = user.id
    const dbUser = await db.user.findUnique({
        where: { email: token.email },
    })
    if (!dbUser) return token
    return {
        id: dbUser.id,
        name: dbUser.name,
        email: dbUser.email,
        picture: dbUser.image,
    }
},

5. 重启服务并清除缓存

修改配置后必须重启Next.js开发服务器,同时清除浏览器Cookie和缓存,避免旧会话残留。


内容的提问来源于stack exchange,提问作者Edris Elbow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 09:02:55