Next.js 13中如何创建类似Next-Auth的SessionProvider上下文?
基于Next.js 13 + JWT实现类似Next-Auth的SessionProvider上下文
我正在用Next.js 13结合JWT做API认证,想创建一个类似Next-Auth的SessionProvider上下文,将其包裹在(dashboard)/layout.tsx组件外层。
现有登录接口代码 (api/auth/sign-in/route.ts)
import { connect } from '@/db/config'; import { NextResponse, NextRequest } from 'next/server'; import User from '@/models/userModel'; import bcryptjs from 'bcryptjs'; import jwt from 'jsonwebtoken'; connect(); export async function POST(req: NextRequest) { try { const reqBody = await req.json(); const { email, password } = reqBody; // 检查用户是否存在 const user = await User.findOne({ email }); if (!user) { return NextResponse.json( { error: '未找到该用户!' }, { status: 400 } ); } // 验证密码正确性 const validPassword = await bcryptjs.compare(password, user.password); if (!validPassword) { return NextResponse.json({ error: '密码错误!' }, { status: 400 }); } // 构造Token数据 const tokenData = { id: user._id, firstname: user.firstname, lastname: user.lastname, email: user.email, isAdmin: user.isAdmin }; // 生成JWT Token const token = await jwt.sign(tokenData, process.env.JWT_SECRET!, { expiresIn: '12h' }); const response = NextResponse.json({ message: '登录成功', success: true }); response.cookies.set('token', token, { httpOnly: true }); return response; } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } }
该接口通过组件级API调用可正常工作,但我希望通过上下文函数在组件层面获取会话中的用户信息及活跃状态。尝试了ChatGPT提供的代码但无法正常运行,代码如下:
'use client'; import React, { createContext, useState, useContext } from 'react'; import jwt from 'jsonwebtoken'; import axios from 'axios'; import { useRouter } from 'next/navigation'; interface UserData { userId: string; email: string; } // 定义上下文类型 interface SessionContextType { user: UserData | null; login: (email: string, password: string) => void; logout: () => void; } // 创建上下文 const SessionContext = createContext<SessionContextType>({ user: null, login: () => {}, logout: () => {} }); export function useSession() { return useContext(SessionContext); } export function SessionProvider({ children }: { children: React.ReactNode }) { const router = useRouter(); const [user, setUser] = useState<UserData | null>(null); const login = async (email: string, password: string) => { try { const res = await axios.post('/api/auth/sign-in', user); router.push('/dashboard'); if (res.data) { const data = await res.data; const token = data.token; // 解码Token获取用户数据并设置状态 const decodedToken: any = jwt.verify(token, process.env.JWT_SECRET!); setUser(decodedToken); } else { // 处理登录错误,显示错误信息等 console.error('登录失败:', res.status); } } catch (error: any) { console.log('登录失败', error.message); } }; const logout = () => { setUser(null); }; return ( <SessionContext.Provider value={{ user, login, logout }}> {children} </SessionContext.Provider> ); }
问题分析与修正方案
原代码存在以下核心问题:
- 登录函数传参错误:
axios.post传入的是user状态而非登录表单的email/password - Token获取逻辑错误:原接口将Token存在Cookie而非响应体,无法从
res.data.token读取 - 数据结构不匹配:
UserData接口与Token中的用户字段不一致 - 缺少会话初始化:页面刷新后不会从Cookie读取Token恢复用户状态
- 登出逻辑不完整:仅清空前端状态未删除Cookie中的Token
1. 修正后的SessionProvider代码
'use client'; import React, { createContext, useState, useContext, useEffect } from 'react'; import axios from 'axios'; import { useRouter } from 'next/navigation'; // 匹配Token中的用户数据结构 interface UserData { id: string; firstname: string; lastname: string; email: string; isAdmin: boolean; } interface SessionContextType { user: UserData | null; login: (email: string, password: string) => Promise<void>; logout: () => Promise<void>; } const SessionContext = createContext<SessionContextType>({ user: null, login: async () => {}, logout: async () => {}, }); export function useSession() { return useContext(SessionContext); } export function SessionProvider({ children }: { children: React.ReactNode }) { const router = useRouter(); const [user, setUser] = useState<UserData | null>(null); // 页面加载时初始化会话:从Cookie读取Token并验证 useEffect(() => { const initSession = async () => { try { const res = await axios.get('/api/auth/me'); if (res.data.success) { setUser(res.data.user); } } catch (error) { setUser(null); } }; initSession(); }, []); const login = async (email: string, password: string) => { try { const res = await axios.post('/api/auth/sign-in', { email, password }); if (res.data.success) { const userRes = await axios.get('/api/auth/me'); setUser(userRes.data.user); router.push('/dashboard'); } else { console.error('登录失败:', res.data.error); } } catch (error: any) { console.error('登录出错:', error.response?.data?.error || error.message); } }; const logout = async () => { try { await axios.post('/api/auth/sign-out'); setUser(null); router.push('/'); } catch (error) { console.error('登出出错:', error); setUser(null); router.push('/'); } }; return ( <SessionContext.Provider value={{ user, login, logout }}> {children} </SessionContext.Provider> ); }
2. 添加用户信息验证接口 (api/auth/me/route.ts)
import { connect } from '@/db/config'; import { NextResponse, NextRequest } from 'next/server'; import User from '@/models/userModel'; import jwt from 'jsonwebtoken'; connect(); export async function GET(req: NextRequest) { try { const token = req.cookies.get('token')?.value || ''; if (!token) { return NextResponse.json({ success: false, error: '未登录' }, { status: 401 }); } const decodedToken: any = jwt.verify(token, process.env.JWT_SECRET!); const user = await User.findById(decodedToken.id).select('-password'); if (!user) { return NextResponse.json({ success: false, error: '用户不存在' }, { status: 404 }); } return NextResponse.json({ success: true, user: { id: user._id, firstname: user.firstname, lastname: user.lastname, email: user.email, isAdmin: user.isAdmin } }); } catch (error: any) { return NextResponse.json({ success: false, error: 'Token无效' }, { status: 401 }); } }
3. 添加登出接口 (api/auth/sign-out/route.ts)
import { NextResponse } from 'next/server'; export async function POST() { const response = NextResponse.json({ message: '登出成功', success: true }); response.cookies.set('token', '', { httpOnly: true, expires: new Date(0) }); return response; }
4. 在Dashboard布局中包裹SessionProvider
import { SessionProvider } from '@/contexts/SessionProvider'; // 其他必要导入 export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { return ( <SessionProvider> {/* 你的Dashboard布局内容 */} {children} </SessionProvider> ); }
5. 在组件中使用useSession
'use client'; import { useSession } from '@/contexts/SessionProvider'; export default function DashboardPage() { const { user, logout } = useSession(); if (!user) { return <div>请先登录</div>; } return ( <div> <h1>欢迎回来,{user.firstname}</h1> <button onClick={logout}>登出</button> </div> ); }
内容的提问来源于stack exchange,提问作者Tech Phoenix
相关产品推荐
相关产品推荐

