Next.js 14及以上版本Next-Auth Google Provider OAuthCallback错误
NextAuth + Google Provider 登录JWT过期错误问题
我使用NextAuth结合Google Provider实现登录功能,已创建下拉菜单,用户点击登录按钮后会跳转到api/auth/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F页面,选择Google登录并完成邮箱密码验证后,未跳转回应用,反而出现错误。
错误日志
GET /api/auth/providers 200 in 30ms GET /api/auth/signin?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2F 200 in 33ms POST /api/auth/signin/google 302 in 1211ms [next-auth][error][OAUTH_CALLBACK_ERROR] https://next-auth.js.org/errors#oauth_callback_error JWT expired, now 1715240896, exp 1715201013 { error: RPError: JWT expired, now 1715240896, exp 1715201013 at Client.validateJWT (webpack-internal:///(rsc)/./node_modules/openid-client/lib/client.js:864:23) at Client.validateIdToken (webpack-internal:///(rsc)/./node_modules/openid-client/lib/client.js:651:64) at Client.callback (webpack-internal:///(rsc)/./node_modules/openid-client/lib/client.js:430:24) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async oAuthCallback (webpack-internal:///(rsc)/./node_modules/next-auth/core/lib/oauth/callback.js:109:16) at async Object.callback (webpack-internal:///(rsc)/./node_modules/next-auth/core/routes/callback.js:52:11) at async AuthHandler (webpack-internal:///(rsc)/./node_modules/next-auth/core/index.js:208:28) at async NextAuthRouteHandler (webpack-internal:///(rsc)/./node_modules/next-auth/next/index.js:69:28) at async NextAuth._args$ (webpack-internal:///(rsc)/./node_modules/next-auth/next/index.js:105:16) at async C:\Next\ecommerce-yt\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:53446 at async e_.execute (C:\Next\ecommerce-yt\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:44747) at async e_.handle (C:\Next\ecommerce-yt\node_modules\next\dist\compiled\next-server\app-route.runtime.dev.js:6:54700) at async doRender (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:1377:42) at async cacheEntry.responseCache.get.routeKind (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:1587:40) at async DevServer.renderToResponseWithComponentsImpl (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:1507:28) at async DevServer.renderPageComponent (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:1924:24) at async DevServer.renderToResponseImpl (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:1962:32) at async DevServer.pipeImpl (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:920:25) at async NextNodeServer.handleCatchallRenderRequest (C:\Next\ecommerce-yt\node_modules\next\dist\server\next-server.js:272:17) at async DevServer.handleRequestImpl (C:\Next\ecommerce-yt\node_modules\next\dist\server\base-server.js:816:17) at async C:\Next\ecommerce-yt\node_modules\next\dist\server\dev\next-dev-server.js:339:20 at async Span.traceAsyncFn (C:\Next\ecommerce-yt\node_modules\next\dist\trace\trace.js:154:20) at async DevServer.handleRequest (C:\Next\ecommerce-yt\node_modules\next\dist\server\dev\next-dev-server.js:336:24) at async invokeRender (C:\Next\ecommerce-yt\node_modules\next\dist\server\lib\router-server.js:174:21) at async handleRequest (C:\Next\ecommerce-yt\node_modules\next\dist\server\lib\router-server.js:353:24) at async requestHandlerImpl (C:\Next\ecommerce-yt\node_modules\next\dist\server\lib\router-server.js:377:13) at async Server.requestListener (C:\Next\ecommerce-yt\node_modules\next\dist\server\lib\start-server.js:141:13) { name: 'OAuthCallbackError', code: undefined }, providerId: 'google', message: 'JWT expired, now 1715240896, exp 1715201013' } GET /api/auth/callback/google?state=WbD1LDf6r0mseWQw3LNo9BWSxlc38QT6L3ZDbe29GvY&code=4%2F0AdLIrYdC9MZFw5WH9ZlesxYhtcDRf7-XRN0rNEwSRZraX_9gUGYwdGe7YJ-6Y9ySbLeHcw&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+openid&authuser=0&prompt=none 302 in 1890ms GET /api/auth/error?error=OAuthCallback 302 in 42ms GET /api/auth/signin?error=OAuthCallback 200 in 31ms
相关代码
1. UserMenuButton.tsx
"use client" import { Session } from 'next-auth' import { signIn, signOut } from 'next-auth/react' import Image from 'next/image' import React from 'react' interface UserMenuButtonProps{ session:Session | null } const UserMenuButton = ({ session }: UserMenuButtonProps) => { const user = session?.user; return ( <div className='dropdown dropdown-end'> <label tabIndex={0} className='btn btn-ghost btn-circle'> {user ? <Image src={user?.image || "abc"} alt={user?.email || "user email"} height={40} width={40} className='w-10 rounded-full'/> : <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" className="inline-block h-5 w-5 stroke-current" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 12h.01M12 12h.01M19 12h.01M6 12a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0zm7 0a1 1 0 11-2 0 1 1 0 012 0z" /> </svg>} </label> <ul tabIndex={0} className='dropdown-content menu rounded-box menu-sm z-30 mt-3 w-52 bg-base-100 p-2 shadow'> <li>{ user ? <button onClick={()=>signOut({callbackUrl:"/"})}>Sign Out</button>: <button onClick={()=>signIn()}>Sign In</button>}</li> </ul> </div> ) } export default UserMenuButton
2. route.tsx
import prisma from "@/lib/db/prisma"; import { env } from "@/lib/env"; import { PrismaAdapter } from "@auth/prisma-adapter"; import { NextAuthOptions } from "next-auth"; import { Adapter } from "next-auth/adapters"; import NextAuth from "next-auth/next"; import GoogleProvider from "next-auth/providers/google"; export const authOptions: NextAuthOptions = { adapter: PrismaAdapter(prisma) as Adapter, providers: [ GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }), ], // ... other NextAuth options events: { async signIn({ user }) { // Perform any user-related actions after successful sign-in (e.g., database updates) // Ensure this logic is optimized and doesn't introduce significant delays console.log("User signed in:", user); // ... }, }, // ... other NextAuth options (e.g., session) }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
解决方案
这个错误核心原因是本地服务器时间与Google服务器时间不一致,导致验证JWT时判定过期。按以下步骤解决:
同步本地系统时间
- Windows:打开设置→时间和语言→开启自动设置时间,同步到互联网时间服务器。
- Mac:系统设置→通用→日期与时间→勾选"自动设置日期与时间"。
- Linux:执行
sudo timedatectl set-ntp true开启自动时间同步。
检查Google OAuth配置
- 确认Google Cloud控制台中,OAuth客户端的授权重定向URI为
http://localhost:3000/api/auth/callback/google(开发环境)。 - 验证环境变量中的
GOOGLE_CLIENT_ID和GOOGLE_CLIENT_SECRET配置正确,未过期。
- 确认Google Cloud控制台中,OAuth客户端的授权重定向URI为
临时绕过JWT验证(仅开发环境)
若时间同步后仍有问题,可在Google Provider配置中添加idTokenVerification: false临时关闭验证(禁止生产环境使用):GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, idTokenVerification: false, })清除浏览器缓存和Cookie
清除旧会话缓存后,重新测试登录流程。
内容的提问来源于stack exchange,提问作者AR SHAH
相关产品推荐
相关产品推荐

