字节笔记本字节笔记本

NextAuth 与 Google 提供者以及自定义的后端 API 结合进行身份验证

2024-06-12

NextAuth 结合 Google 身份验证提供者和自定义后端 API 实现用户身份验证,包括安装 NextAuth、配置 Google Provider、使用环境变量、保护页面和 API 路由,以及后端使用 Go 和 Gin 框架处理验证和生成自定义 token。

  1. 安装 NextAuth
    在你的 Next.js 项目中安装 NextAuth 和所需的依赖项:

    npm install next-auth
    
  2. 配置 Google Provider
    pages/api/auth/[...nextauth].js 文件中配置 NextAuth,添加 Google Provider,并配置相关的回调函数。

    import NextAuth from "next-auth";
    import GoogleProvider from "next-auth/providers/google";
    
    export default NextAuth({
      providers: [
        GoogleProvider({
          clientId: process.env.GOOGLE_CLIENT_ID,
          clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        })
      ],
      callbacks: {
        async jwt({ token, account, profile }) {
          // 初次登录时,将 JWT token 保存到自定义后端
          if (account && profile) {
            const res = await fetch("https://your-backend-api.com/auth/google", {
              method: 'POST',
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({
                idToken: account.id_token,
                profile,
              })
            });
            const data = await res.json();
    
            // 将后端返回的自定义 token 保存到 NextAuth token 中
            token.backendToken = data.token;
          }
          return token;
        },
        async session({ session, token }) {
          // 在会话对象中包含自定义 token
          session.user.backendToken = token.backendToken;
          return session;
        }
      },
      pages: {
        signIn: '/auth/signin',
      }
    });
    
  3. 在环境变量中配置 Google 客户端 ID 和密钥
    在你的 .env.local 文件中添加以下环境变量:

    GOOGLE_CLIENT_ID=your-google-client-id
    GOOGLE_CLIENT_SECRET=your-google-client-secret
    
  4. 保护页面或 API 路由
    使用 NextAuth 提供的 getSessionuseSession 来保护你的页面或 API 路由,并通过自定义 token 访问后端 API。

    // 在页面组件中使用 useSession
    import { useSession } from "next-auth/react";
    
    export default function ProtectedPage() {
      const { data: session, status } = useSession();
    
      if (status === "loading") {
        return <div>Loading...</div>;
      }
    
      if (!session) {
        return <div>Access Denied</div>;
      }
    
      return (
        <div>
          <h1>Protected Page</h1>
          <p>Welcome, {session.user.name}</p>
        </div>
      );
    }
    
    // 在 API 路由中使用 getSession
    import { getSession } from "next-auth/react";
    
    export default async (req, res) => {
      const session = await getSession({ req });
    
      if (!session) {
        res.status(401).json({ message: "Unauthorized" });
      } else {
        const backendToken = session.user.backendToken;
        // 使用 backendToken 访问自定义后端 API
        const response = await fetch("https://your-backend-api.com/protected-route", {
          method: 'GET',
          headers: { Authorization: `Bearer ${backendToken}` },
        });
        const data = await response.json();
        res.status(200).json(data);
      }
    };
    

后端配置(Go + Gin)

  1. 创建一个 Go 项目并安装 Gin:

    go mod init your-backend
    go get -u github.com/gin-gonic/gin
    
  2. 在你的主文件(例如 main.go)中配置 Gin 路由,并处理 /auth/google 路由:

    package main
    
    import (
      "encoding/json"
      "net/http"
      "github.com/gin-gonic/gin"
    )
    
    type GoogleAuthRequest struct {
      IDToken string                 `json:"idToken"`
      Profile map[string]interface{} `json:"profile"`
    }
    
    func main() {
      r := gin.Default()
    
      r.POST("/auth/google", func(c *gin.Context) {
        var req GoogleAuthRequest
        if err := c.ShouldBindJSON(&req); err != nil {
          c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
          return
        }
    
        // 验证 ID Token (此处省略,假设已经验证通过)
        // 使用 req.IDToken 和 req.Profile 处理用户信息
    
        // 生成自定义 token,示例中简单返回一个静态 token
        customToken := "your-custom-token"
    
        c.JSON(http.StatusOK, gin.H{"token": customToken})
      })
    
      r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
    }
    

后端配置(Go + Gin)加上 验证

  1. 使用 Google 的验证库验证 idtoken

    package main
    
    import (
      "context"
      "encoding/json"
      "net/http"
      "github.com/gin-gonic/gin"
      "google.golang.org/api/idtoken"
    )
    
    type GoogleAuthRequest struct {
      IDToken string                 `json:"idToken"`
      Profile map[string]interface{} `json:"profile"`
    }
    
    func main() {
      r := gin.Default()
    
      r.POST("/auth/google", func(c *gin.Context) {
        var req GoogleAuthRequest
        if err := c.ShouldBindJSON(&req); err != nil {
          c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
          return
        }
    
        // 验证 ID Token
        tokenInfo, err := idtoken.Validate(context.Background(), req.IDToken, "your-google-client-id")
        if err != nil {
          c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid ID token"})
          return
        }
    
        // 使用 tokenInfo 和 req.Profile 处理用户信息
        // tokenInfo 中包含了用户的身份信息,可以进一步验证和使用
    
        // 生成自定义 token,示例中简单返回一个静态 token
        customToken := "your-custom-token"
    
        c.JSON(http.StatusOK, gin.H{"token": customToken})
      })
    
      r.Run(":8080") // 监听并在 0.0.0.0:8080 上启动服务
    }
    
  2. 添加 google.golang.org/api/idtoken 依赖:

    go get google.golang.org/api/idtoken