Next.js集成Auth.js部署时遇CallbackRouteError证书过期问题
本地运行Next.js结合Auth.js的Credentials Provider登录功能正常,但部署到服务器后触发CallbackRouteError,错误详情显示证书过期(CERT_HAS_EXPIRED)。
登录页面组件代码
'use client' import { Label } from '@/components/ui/label' import { Button } from '@/components/ui/button' import * as z from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { FormError } from '@/components/utility/FormError' import { LoginChema } from '@/schemas/LoginSchema' import { login } from '@/actions/login' import { useState, useTransition } from 'react' export const LoginTab = () => { const [error, setError] = useState<string | undefined>('') const [isPending, startTransition] = useTransition() const form = useForm<z.infer<typeof LoginChema>>({ resolver: zodResolver(LoginChema), defaultValues: { login: '', password: '' } }) async function onSubmit(values: z.infer<typeof LoginChema>) { setError('') startTransition(() => { login(values).then((data) => { setError(data?.error) }) }) } return ( <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className='flex flex-col gap-6'> <div className='flex w-full flex-col gap-2'> <Label htmlFor='login_type' className='text-base tracking-tight'> Login/Parolni kiriting </Label> <FormField control={form.control} name='login' render={({ field }) => ( <FormItem> <FormLabel>Login</FormLabel> <FormControl> <Input disabled={isPending} placeholder='login' {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <FormField control={form.control} name='password' render={({ field }) => ( <FormItem> <FormLabel>Parol</FormLabel> <FormControl> <Input disabled={isPending} type='password' placeholder='parol' {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> </div> <FormError message={error} /> <div className='flex w-full flex-col gap-3'> <Button disabled={isPending} type='submit' className='w-full bg-brandHighlight-500 text-white'> Kirish </Button> </div> </form> </Form> ) }
登录Action配置代码
import Credentials from 'next-auth/providers/credentials' import { AuthError, NextAuthConfig } from 'next-auth' import { LoginChema } from '@/schemas/LoginSchema' const baseURL = process.env.NEXT_PUBLIC_API_URL + '/users/login' export default { providers: [ Credentials({ async authorize(credentials) { const validatedFields = LoginChema.safeParse(credentials) if (validatedFields.success) { const { login, password } = validatedFields.data const res = await fetch(baseURL, { method: 'POST', body: JSON.stringify({ username: login, password }), headers: { 'Content-Type': 'application/json' } }) if (!res.ok) { throw new AuthError('CredentialsSignin') } const user = await res.json() if (!user) return null return user } return null } }) ] } satisfies NextAuthConfig
Auth配置文件代码
import NextAuth from 'next-auth' import authConfig from '@/auth.config' export const { handlers: { GET, POST }, auth, signIn, signOut } = NextAuth({ pages: { signIn: 'auth/login', error: '/auth/error' }, callbacks: { async session({ session, token }) { session.user.access_token = token.access_token session.user.username = token.username session.user.role = token.role return session }, async jwt({ token, user }) { if (user) { token.access_token = user.access_token token.username = user.username token.role = user.role } return token } }, session: { strategy: 'jwt', maxAge: 8 * 60 * 60 }, ...authConfig })
报错信息
[auth][error] CallbackRouteError
[auth][cause]: TypeError: fetch failed
at node:internal/deps/undici/undici:12344:11
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async globalThis.fetch (/root/projects/shaffof-open-data/.next/server/chunks/198.js:6:55448)
at async Object.authorize (/root/projects/shaffof-open-data/.next/server/app/auth/login/page.js:1:114277)
...
[auth][details]: { "code": "CERT_HAS_EXPIRED", "provider": "credentials" }
解决方案
1. 检查目标API的SSL证书
报错核心是CERT_HAS_EXPIRED,说明服务器请求的baseURL对应的API网站SSL证书已过期:
- 直接在浏览器访问该API地址,查看地址栏锁图标确认证书状态
- 联系API服务方更新SSL证书
2. 临时绕过证书验证(仅测试环境)
如果是测试环境且无法立即更新证书,可在fetch请求中添加证书跳过配置(生产环境绝对禁止):
import https from 'https' // ...其他代码 const res = await fetch(baseURL, { method: 'POST', body: JSON.stringify({ username: login, password }), headers: { 'Content-Type': 'application/json' }, // 仅测试用,生产环境删除 agent: new https.Agent({ rejectUnauthorized: false }) })
3. 同步服务器系统时间
服务器系统时间偏差过大也会导致证书验证失败:
- 执行
date命令查看服务器当前时间 - 使用
ntpdate或系统自带时间同步工具校准时间
4. 确认环境变量配置
检查服务器上的NEXT_PUBLIC_API_URL是否正确,确认未指向使用过期证书的错误域名。
内容的提问来源于stack exchange,提问作者Islom

