xDocxDoc
AI
前端
后端
iOS
Android
Flutter
AI
前端
后端
iOS
Android
Flutter
  • Next.js 与 Next-Auth V5 实现 Social Logins

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 客户端

  1. 访问 https://console.cloud.google.com/
  2. 创建新项目或选择现有项目
  3. 导航到 "API和服务" > "凭据"
  4. 点击 "创建凭据" > "OAuth客户端ID"
  5. 选择 "Web应用程序" 类型
  6. 配置授权设置:
    • 授权 JavaScript 来源:http://localhost:3000
    • 授权重定向 URI:http://localhost:3000/api/auth/callback/google

配置环境变量

在 .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 应用

  1. 访问 GitHub https://github.com/settings/developers
  2. 点击 "New OAuth App"
  3. 填写应用信息:
    • 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 实现登录功能。以下是实现过程中的关键要点:

核心步骤

  1. 项目初始化:创建 Next.js 项目并安装 Next-Auth V5
  2. 环境配置:设置安全密钥和 OAuth 提供商凭据
  3. Auth.js 配置:创建认证配置文件和 API 路由处理器
  4. UI 实现:构建登录界面和用户信息展示页面
  5. 服务端操作:实现登录和登出的服务器端逻辑
  6. OAuth 集成:配置 Google 和 GitHub 提供商
  7. 会话管理:实现用户会话的保护和管理
最后更新: 2025/10/10 14:27