Next.js 与 Next-Auth V5 实现 Social Logins
在现代 Web 应用中,身份验证和授权是不可或缺的核心功能。身份验证让应用知道用户是谁,而授权则决定用户能够访问哪些资源。本文将深入讲解如何在 Next.js 应用中利用 Next-Auth V5(也称为 Auth.js)实现Social Login功能,特别是通过 Google 和 GitHub 的 OAuth。
身份验证与授权基础
身份验证(Authentication)
身份验证是确认用户身份的过程。常见的方式包括:
- 基于凭证的验证:用户名/密码
- OTP验证:一次性密码
- 魔法链接:通过邮件发送登录链接
- 生物识别:指纹、面部识别
- Social Login:通过第三方平台(如Google、GitHub)验证
授权(Authorization)
授权是决定已认证用户能够访问哪些资源的过程。常见的授权模型包括:
- 基于角色的访问控制(RBAC):根据用户角色分配权限
- 基于策略的访问控制(PBAC):通过策略规则控制访问
Next-Auth V5 简介
Auth.js 是一个开源身份验证库,可与现代 JavaScript 框架(如 Next.js、Svelte 和 Express)集成。之前该项目仅支持 Next.js,因此被称为 Next-Auth。现在,Auth.js 仍然支持 Next-Auth,同时还提供了 SvelteKitAuth 和 ExpressAuth。
主要特性
- 支持多种身份验证提供商(OAuth、Email、Credentials等)
- 完整的类型安全支持(TypeScript)
- 无缝的服务器端渲染(SSR)支持
- 安全的默认配置和灵活的扩展选项
项目初始化与设置
创建 Next.js 项目
首先,使用以下命令创建新的 Next.js 项目:
npx create-next-app@latest
在创建过程中,系统会提示您进行以下配置选择:
- 项目名称
- 使用 JavaScript 或 TypeScript
- 是否启用 ESLint
- 是否使用 Tailwind CSS
- 是否使用
src/
目录 - 是否使用 App Router(必须启用)
- 是否自定义默认导入别名
建议选择以下配置:
- JavaScript(或 TypeScript,根据偏好)
- 启用 Tailwind CSS
- 使用 App Router
- 其他选项根据需求选择
安装 Next-Auth V5
Next-Auth V5 目前处于测试阶段,安装时需指定 beta 版本:
# 使用 yarn
yarn add next-auth@beta
# 使用 npm
npm install next-auth@beta
# 使用 pnpm
pnpm add next-auth@beta
# 使用 bun
bun add next-auth@beta
环境配置与密钥生成
生成认证密钥
Next-Auth 需要设置一个安全的密钥用于加密会话令牌。在项目根目录创建 .env.local
文件,并添加 AUTH_SECRET
环境变量。
生成密钥的方法:
# 使用 auth 命令行工具
npx auth secret
# 或者在 Linux/Mac 系统上使用 OpenSSL
openssl rand -base64 33
将生成的密钥添加到 .env.local
文件中:
AUTH_SECRET=your-generated-secret-here
其他环境变量
后续我们将添加更多环境变量来存储 OAuth 提供商的客户端 ID 和密钥。
Auth.js 核心配置
创建 auth.js 文件
在项目根目录创建 auth.js
文件,配置 Next-Auth 的核心设置:
// 导入 NextAuth 和提供商
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
// 导出认证相关方法和对象
export const {
handlers: { GET, POST }, // 用于 API 路由的处理器
auth, // 获取会话信息的函数
signIn, // 登录函数
signOut, // 登出函数
} = NextAuth({
// 配置认证提供商
providers: [
// 这里将添加 Google 和 GitHub 提供商配置
],
// 可选:自定义页面路径
pages: {
signIn: '/auth/signin', // 自定义登录页面
error: '/auth/error', // 错误显示页面
},
// 会话配置
session: {
strategy: 'jwt', // 使用 JWT 作为会话策略
maxAge: 30 * 24 * 60 * 60, // 30天会话有效期
},
// 回调函数
callbacks: {
async jwt({ token, user, account }) {
// 在 JWT 令牌创建或更新时调用
if (user) {
token.id = user.id; // 将用户ID添加到令牌
}
if (account) {
token.accessToken = account.access_token; // 添加访问令牌
}
return token;
},
async session({ session, token }) {
// 在会话创建时调用
if (session?.user) {
session.user.id = token.id; // 将用户ID添加到会话
}
return session;
},
},
});
创建 API 路由处理器
Next-Auth 需要一个 API 端点来处理认证请求。创建以下文件结构:
app/
api/
auth/
[...nextauth]/
route.js
在 route.js
文件中添加以下代码:
// 从 auth.js 导入 GET 和 POST 处理器
export { GET, POST } from "@/auth";
这个动态路由 ([...nextauth]
) 会捕获所有匹配 /api/auth/*
的请求,并将其交给 Next-Auth 处理。
实现登录界面
创建登录表单组件
在项目中创建 components/LoginForm.jsx
文件:
// 导入服务器操作
import { doSocialLogin } from "@/app/actions";
// 登录表单组件
const LoginForm = () => {
return (
<form action={doSocialLogin} className="flex flex-col space-y-4">
<button
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-md transition duration-200"
type="submit"
name="action"
value="google"
>
<div className="flex items-center justify-center">
使用 Google 登录
</div>
</button>
<button
className="bg-gray-800 hover:bg-gray-900 text-white font-semibold py-2 px-4 rounded-md transition duration-200"
type="submit"
name="action"
value="github"
>
<div className="flex items-center justify-center">
使用 GitHub 登录
</div>
</button>
</form>
);
};
export default LoginForm;
更新主页面
修改 app/page.js
文件以使用登录表单:
// 导入登录表单组件
import LoginForm from "@/components/LoginForm";
// 主页组件
export default function Home() {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h1 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
欢迎来到我们的应用
</h1>
<p className="mt-2 text-center text-sm text-gray-600">
请选择以下方式登录
</p>
</div>
<LoginForm />
</div>
</div>
);
}
服务端操作处理
创建服务器操作
在 Next.js 中,服务器操作是在服务端执行的函数,可以通过客户端组件调用。创建 app/actions/index.js
文件:
// 服务器操作指令
'use server'
// 导入 Next-Auth 的登录和登出方法
import { signIn, signOut } from "@/auth";
/**
* 处理Social Login的服务器操作
* @param {FormData} formData - 表单数据
*/
export async function doSocialLogin(formData) {
// 获取操作类型(google 或 github)
const action = formData.get('action');
try {
// 调用 Next-Auth 的 signIn 方法
await signIn(action, {
redirectTo: "/home", // 登录成功后重定向到首页
});
} catch (error) {
// 处理登录错误
console.error('登录错误:', error);
throw new Error('登录失败,请稍后重试');
}
}
/**
* 处理用户登出的服务器操作
*/
export async function doLogout() {
try {
// 调用 Next-Auth 的 signOut 方法
await signOut({
redirectTo: "/", // 登出后重定向到根路径
});
} catch (error) {
// 处理登出错误
console.error('登出错误:', error);
throw new Error('登出失败,请稍后重试');
}
}
/**
* 获取当前用户会话信息
* @returns {Promise<Object>} 会话信息
*/
export async function getSession() {
// 导入 auth 函数
const { auth } = await import('@/auth');
// 获取当前会话
const session = await auth();
return session;
}
Google OAuth 集成
创建 Google OAuth 客户端
- 访问 https://console.cloud.google.com/
- 创建新项目或选择现有项目
- 导航到 "API和服务" > "凭据"
- 点击 "创建凭据" > "OAuth客户端ID"
- 选择 "Web应用程序" 类型
- 配置授权设置:
- 授权 JavaScript 来源:
http://localhost:3000
- 授权重定向 URI:
http://localhost:3000/api/auth/callback/google
- 授权 JavaScript 来源:
配置环境变量
在 .env.local
文件中添加 Google OAuth 凭据:
AUTH_SECRET=your-auth-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
更新 Auth.js 配置
修改 auth.js
文件,添加 Google 提供商:
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent", // 每次登录都要求用户同意
access_type: "offline", // 获取刷新令牌
response_type: "code", // 使用授权码流程
},
},
// 配置用户资料映射
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture,
};
},
}),
],
});
GitHub OAuth 集成
创建 GitHub OAuth 应用
- 访问 GitHub https://github.com/settings/developers
- 点击 "New OAuth App"
- 填写应用信息:
- Application name: 您的应用名称
- Homepage URL:
http://localhost:3000
- Authorization callback URL:
http://localhost:3000/api/auth/callback/github
配置环境变量
在 .env.local
文件中添加 GitHub OAuth 凭据:
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
更新 Auth.js 配置
修改 auth.js
文件,添加 GitHub 提供商:
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GitHubProvider from "next-auth/providers/github";
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
providers: [
GoogleProvider({
// ... Google 配置保持不变
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
// 配置用户资料映射
profile(profile) {
return {
id: profile.id.toString(),
name: profile.name || profile.login,
email: profile.email,
image: profile.avatar_url,
};
},
}),
],
});
用户会话管理
创建受保护的主页
创建 app/home/page.js
文件,显示登录用户信息:
// 导入所需组件和函数
import Image from "next/image";
import Logout from "@/components/Logout";
import { auth } from "@/auth";
import { redirect } from "next/navigation";
/**
* 受保护的主页组件
* 只有已认证用户才能访问
*/
const HomePage = async () => {
// 获取当前会话信息
const session = await auth();
// 如果用户未认证,重定向到登录页面
if (!session?.user) {
redirect("/");
}
return (
<div className="min-h-screen bg-gray-100 flex flex-col items-center justify-center">
<div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h1 className="text-2xl font-bold text-center mb-6">
欢迎, {session?.user?.name}!
</h1>
<div className="flex flex-col items-center mb-6">
<Image
src={session?.user?.image}
alt={session?.user?.name}
width={96}
height={96}
className="rounded-full mb-4"
/>
<p className="text-gray-600">{session?.user?.email}</p>
</div>
<div className="bg-gray-50 p-4 rounded-md mb-6">
<h2 className="font-semibold mb-2">会话信息</h2>
<pre className="text-xs overflow-auto">
{JSON.stringify(session, null, 2)}
</pre>
</div>
<Logout />
</div>
</div>
);
};
export default HomePage;
创建登出组件
创建 components/Logout.jsx
文件:
// 导入服务器操作
import { doLogout } from "@/app/actions";
/**
* 登出组件
*/
const Logout = () => {
return (
<form action={doLogout}>
<button
className="w-full bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded-md transition duration-200"
type="submit"
>
安全退出
</button>
</form>
);
};
export default Logout;
配置 Next.js 图像域名
更新 next.config.js
文件以允许从 Google 和 GitHub 加载图像:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com', // Google 头像域名
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com', // GitHub 头像域名
},
],
},
};
export default nextConfig;
安全配置与最佳实践
增强安全配置
在 auth.js
中添加更多安全配置:
export const {
// ... 导出保持不变
} = NextAuth({
// ... 提供商配置保持不变
// 安全相关配置
cookies: {
sessionToken: {
name: `next-auth.session-token`,
options: {
httpOnly: true, // 防止XSS攻击
sameSite: 'lax', // CSRF保护
path: '/',
secure: process.env.NODE_ENV === 'production', // 仅在生产环境使用HTTPS
},
},
},
// 高级安全配置
advanced: {
// 启用服务器端会话管理
useSecureCookies: process.env.NODE_ENV === 'production',
},
});
环境验证
创建 utility 函数验证环境变量:
// lib/env.js
/**
* 验证必需的环境变量
* @param {Array} requiredVars - 必需的环境变量名称数组
*/
export function validateEnv(requiredVars) {
const missingVars = requiredVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`);
}
}
// 在应用启动时验证环境变量
validateEnv(['AUTH_SECRET', 'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET', 'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET']);
错误处理中间件
创建自定义错误处理:
// middleware/errorHandler.js
/**
* 认证错误处理中间件
*/
export function withAuthErrorHandling(handler) {
return async (req, res) => {
try {
return await handler(req, res);
} catch (error) {
console.error('认证错误:', error);
// 根据错误类型返回不同的错误页面
if (error.type === 'CredentialsSignin') {
return res.redirect('/auth/error?error=CredentialsSignin');
}
return res.redirect('/auth/error?error=UnknownError');
}
};
}
总结
我们了解了如何在 Next.js 应用中集成 Next-Auth V5 实现登录功能。以下是实现过程中的关键要点:
核心步骤
- 项目初始化:创建 Next.js 项目并安装 Next-Auth V5
- 环境配置:设置安全密钥和 OAuth 提供商凭据
- Auth.js 配置:创建认证配置文件和 API 路由处理器
- UI 实现:构建登录界面和用户信息展示页面
- 服务端操作:实现登录和登出的服务器端逻辑
- OAuth 集成:配置 Google 和 GitHub 提供商
- 会话管理:实现用户会话的保护和管理