Laravel Breeze+Next.js Google登录及Next.js认证中间件配置
已实现内容
Laravel Breeze控制器代码
public function handleAuthCallback(): JsonResponse { /** @var SocialiteUser $socialiteUser */ try { $socialiteUser = Socialite::driver('google')->stateless()->user(); } catch (ClientException $e) { return response()->json(['error' => 'Invalid credentials provided.'], 422); } /** @var User $user */ $user = User::query() ->firstOrCreate( [ 'email' => $socialiteUser->getEmail(), ], [ 'email_verified_at' => now(), 'name' => $socialiteUser->getName(), 'google_id' => $socialiteUser->getId(), 'password' => Hash::make(Str::random(40)) ] ); event(new Registered($user)); Auth::login($user); return response()->json([ 'user' => $user, 'access_token' => $user->createToken('google-token')->plainTextToken, 'token_type' => 'Bearer', ]); }
Next.js端代码
import React, { useState, useEffect } from 'react' import { useRouter } from 'next/router' function google() { const Router = useRouter() const [loading, setLoading] = useState(true) const [data, setData] = useState({}) const [user, setUser] = useState(null) // On page load, we take "search" parameters // and proxy them to /api/auth/callback on our Laravel API const p = Router.asPath const q = p.split('?', 2) const query = q[q.length - 1] useEffect(() => { fetch(`http://localhost:8000/login/google/callback?${query}`, { headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, }) .then(response => { return response.json() }) .then(data => { setLoading(false) setData(data) }) }, []) useEffect(() => { window.sessionStorage.setItem('tb:user', JSON.stringify(data)) console.log(JSON.stringify(data, null, 2)) }) // Helper method to fetch User data for authenticated user // Watch out for "Authorization" header that is added to this call function fetchUserData() { fetch(`http://localhost:8000/api/user`, { headers: { 'Content-Type': 'application/json', Accept: 'application/json', Authorization: 'Bearer ' + data.access_token, }, }) .then(response => { return response.json() }) .then(data => { setUser(data) }) } if (loading) { return <DisplayLoading /> } else { if (user != null) { return <DisplayData data={user} /> } else { return ( <div> <DisplayData data={data} /> <div style={{ marginTop: 10 }}> <button onClick={fetchUserData}>Fetch User</button> </div> </div> ) } } } function DisplayLoading() { return <div>Loading....</div> } function DisplayData(data) { return ( <div> <samp>{JSON.stringify(data, null, 2)}</samp> </div> ) } export default google
问题
已完成上述Google登录相关代码开发,但登录后无法获取用户信息,请问如何在Next.js中配置认证中间件以实现用户身份验证?
解决方案
1. 先修复现有代码的问题
你的Next.js代码存在两处影响功能的问题:
- 第二个
useEffect无依赖项,会在每次渲染时执行,可能在data未获取完成就往sessionStorage存入空值,修改如下:
useEffect(() => { if (data.access_token) { window.sessionStorage.setItem('tb:user', JSON.stringify(data)) console.log(JSON.stringify(data, null, 2)) } }, [data])
- 调用
fetchUserData前需判断data.access_token是否存在,避免生成无效的Authorization请求头。
2. App Router版本(Next.js 13+)配置认证中间件
步骤1:创建中间件文件
在项目根目录新建middleware.ts:
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // 从Cookie中读取token(中间件运行在服务端,无法访问sessionStorage) const token = request.cookies.get('access_token')?.value // 定义需要认证的路由 const protectedRoutes = ['/dashboard', '/profile'] const isProtectedRoute = protectedRoutes.some(route => request.nextUrl.pathname.startsWith(route)) // 未登录且访问受保护路由时,跳转到登录页 if (isProtectedRoute && !token) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next() } // 指定中间件生效的路由范围 export const config = { matcher: ['/dashboard/:path*', '/profile/:path*', '/api/protected/:path*'], }
步骤2:登录成功后将Token存入Cookie
修改Next.js登录页的回调逻辑,把token同时存入Cookie(让中间件能读取到):
.then(data => { setLoading(false) setData(data) // 存入Cookie,有效期设为1天 document.cookie = `access_token=${data.access_token}; max-age=86400; path=/; same-site=lax` })
步骤3:封装带认证的请求函数
创建统一的请求工具,自动携带Token:
export const authFetch = async (url: string, options: RequestInit = {}) => { const token = document.cookie.split('; ').find(row => row.startsWith('access_token='))?.split('=')[1] const headers = { 'Content-Type': 'application/json', Accept: 'application/json', ...options.headers, } if (token) { headers['Authorization'] = `Bearer ${token}` } return fetch(url, { ...options, headers }) }
之后用这个函数替代原生fetch,比如fetchUserData:
function fetchUserData() { authFetch(`http://localhost:8000/api/user`) .then(response => response.json()) .then(data => setUser(data)) }
3. Pages Router版本配置认证中间件
如果使用旧版Pages Router,在pages/_middleware.ts中编写逻辑:
import type { NextFetchEvent, NextRequest } from 'next/server' import { NextResponse } from 'next/server' export function middleware(req: NextRequest, ev: NextFetchEvent) { const token = req.cookies.get('access_token')?.value const protectedPaths = ['/dashboard', '/profile'] if (protectedPaths.includes(req.nextUrl.pathname) && !token) { return NextResponse.redirect('/login') } return NextResponse.next() }
4. 简化方案:使用NextAuth.js
如果不想手动处理Token和中间件,可以用NextAuth.js集成Laravel认证:
- 安装依赖:
npm install next-auth - 创建
pages/api/auth/[...nextauth].ts:
import NextAuth from 'next-auth' import GoogleProvider from 'next-auth/providers/google' import axios from 'axios' export default NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], callbacks: { async signIn({ account, profile }) { // 将Google授权码传给Laravel后端获取Token const res = await axios.post('http://localhost:8000/login/google/callback', { code: account?.code, }) return res.data.access_token ? true : false }, async session({ session, token }) { // 将Token注入session,方便页面调用 session.accessToken = token.accessToken return session }, }, })
之后通过useSession钩子即可获取用户认证状态,NextAuth会自动处理路由拦截和Token管理。
内容的提问来源于stack exchange,提问作者Taufik Hizet
相关产品推荐
相关产品推荐

