React与Express应用集成新版Google认证完整指南

本文详细介绍了如何在React.js和Express.js应用中配置新版Google认证,包括生成客户端ID、设置前后端环境、实现认证流程及代码示例,帮助开发者构建安全的用户登录系统。

如何在React和Express应用中设置新版Google认证

主要内容概述

本文将指导您如何在React.js和Express.js应用中配置新版"使用Google登录"按钮。随着Google身份服务SDK的更新,集成Google登录功能变得更加简单和强大。

生成Google客户端ID和密钥

步骤1:访问Google控制台

首先前往Google控制台。

步骤2:创建新项目

点击顶部导航中的项目下拉菜单,选择"新建项目"。

步骤3:输入项目详情

输入项目名称(如connect-google-auth-2024),然后点击"创建"。

步骤4:配置OAuth同意屏幕

从左侧菜单点击"API和服务",然后点击"OAuth同意屏幕"进行配置。

步骤5:创建凭据

转到"凭据",创建新的OAuth 2.0客户端ID:

  • 应用类型:Web应用
  • 授权重定向URI:添加http://localhost和http://localhost:3000

步骤6:下载客户端ID和密钥

成功存储凭据后,可以复制或下载生成的客户端ID和密钥。

设置React应用

使用Create React App初始化React.js应用:

1
2
npx create-react-app app
npm install @react-oauth/google

设置Express服务器

创建server文件夹并安装所需包:

1
npm install express cors dotenv google-auth-library jsonwebtoken nodemon

配置package.json脚本:

1
2
3
4
5
6
{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}

服务器基本配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const express = require("express");
const app = express();
require("dotenv/config");
const cors = require("cors");
const { OAuth2Client } = require("google-auth-library");
const jwt = require("jsonwebtoken");

app.use(cors({
  origin: ["http://localhost:3000"],
  methods: "GET,POST,PUT,DELETE,OPTIONS",
}));
app.use(express.json());

let DB = [];

app.listen("5152", () => console.log("Server running on port 5152"));

准备React应用

在index.html中添加Google脚本:

1
<script src="https://accounts.google.com/gsi/client" async defer></script>

创建项目结构:

  • src/screens/ (包含Home.jsx, Landing.jsx, Login.jsx, Signup.jsx, index.js)
  • src/hooks/ (包含useFetch.jsx)

配置客户端路由

安装react-router-dom:

1
npm install react-router-dom

更新App.js路由配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";

const App = () => {
  const [user, setUser] = useState({});
  
  useEffect(() => {
    const theUser = localStorage.getItem("user");
    if (theUser && !theUser.includes("undefined")) {
      setUser(JSON.parse(theUser));
    }
  }, []);

  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={user?.email ? <Navigate to="/home" /> : <Landing />} />
        <Route path="/signup" element={user?.email ? <Navigate to="/home" /> : <Signup />} />
        <Route path="/login" element={user?.email ? <Navigate to="/home" /> : <Login />} />
        <Route path="/home" element={user?.email ? <Home user={user} /> : <Navigate to="/" />} />
      </Routes>
    </BrowserRouter>
  );
};

创建登录页面组件

登录页面(Login.jsx)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import useFetch from "../hooks/useFetch";

const Login = () => {
  const { handleGoogle, loading, error } = useFetch("http://localhost:5152/login");

  useEffect(() => {
    if (window.google) {
      google.accounts.id.initialize({
        client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
        callback: handleGoogle,
      });

      google.accounts.id.renderButton(document.getElementById("loginDiv"), {
        theme: "filled_black",
        text: "signin_with",
        shape: "pill",
      });
    }
  }, [handleGoogle]);

  return (
    <>
      <nav style={{ padding: "2rem" }}>
        <Link to="/">Go Back</Link>
      </nav>
      <header style={{ textAlign: "center" }}>
        <h1>Login to continue</h1>
      </header>
      <main style={{ display: "flex", justifyContent: "center", flexDirection: "column", alignItems: "center" }}>
        {error && <p style={{ color: "red" }}>{error}</p>}
        {loading ? <div>Loading....</div> : <div id="loginDiv"></div>}
      </main>
    </>
  );
};

创建useFetch Hook

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { useState } from "react";

const useFetch = (url) => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  const handleGoogle = async (response) => {
    setLoading(true);
    fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ credential: response.credential }),
    })
      .then((res) => {
        setLoading(false);
        return res.json();
      })
      .then((data) => {
        if (data?.user) {
          localStorage.setItem("user", JSON.stringify(data?.user));
          window.location.reload();
        }
        throw new Error(data?.message || data);
      })
      .catch((error) => {
        setError(error?.message);
      });
  };

  return { loading, error, handleGoogle };
};

创建服务器端路由

验证Google令牌函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
const client = new OAuth2Client(GOOGLE_CLIENT_ID);

async function verifyGoogleToken(token) {
  try {
    const ticket = await client.verifyIdToken({
      idToken: token,
      audience: GOOGLE_CLIENT_ID,
    });
    return { payload: ticket.getPayload() };
  } catch (error) {
    return { error: "Invalid user detected. Please try again" };
  }
}

注册路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
app.post("/signup", async (req, res) => {
  try {
    if (req.body.credential) {
      const verificationResponse = await verifyGoogleToken(req.body.credential);
      
      if (verificationResponse.error) {
        return res.status(400).json({ message: verificationResponse.error });
      }

      const profile = verificationResponse?.payload;
      DB.push(profile);

      res.status(201).json({
        message: "Signup was successful",
        user: {
          firstName: profile?.given_name,
          lastName: profile?.family_name,
          picture: profile?.picture,
          email: profile?.email,
          token: jwt.sign({ email: profile?.email }, process.env.JWT_SECRET, { expiresIn: "1d" }),
        },
      });
    }
  } catch (error) {
    res.status(500).json({ message: "An error occurred. Registration failed." });
  }
});

登录路由

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
app.post("/login", async (req, res) => {
  try {
    if (req.body.credential) {
      const verificationResponse = await verifyGoogleToken(req.body.credential);
      
      if (verificationResponse.error) {
        return res.status(400).json({ message: verificationResponse.error });
      }

      const profile = verificationResponse?.payload;
      const existsInDB = DB.find((person) => person?.email === profile?.email);

      if (!existsInDB) {
        return res.status(400).json({ message: "You are not registered. Please sign up" });
      }

      res.status(201).json({
        message: "Login was successful",
        user: {
          firstName: profile?.given_name,
          lastName: profile?.family_name,
          picture: profile?.picture,
          email: profile?.email,
          token: jwt.sign({ email: profile?.email }, process.env.JWT_SECRET, { expiresIn: "1d" }),
        },
      });
    }
  } catch (error) {
    res.status(500).json({ message: error?.message || error });
  }
});

创建主页组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import React from "react";

const Home = ({ user }) => {
  const logout = () => {
    localStorage.removeItem("user");
    window.location.reload();
  };
  
  return (
    <div style={{ textAlign: "center", margin: "3rem" }}>
      <h1>Dear {user?.email}</h1>
      <p>You are viewing this page because you are logged in or you just signed up</p>
      <div>
        <button onClick={logout} style={{ color: "red", border: "1px solid gray", backgroundColor: "white", padding: "0.5rem 1rem", cursor: "pointer" }}>
          Logout
        </button>
      </div>
    </div>
  );
};

与其他认证方法的比较

方法 优点 缺点
Google认证 无缝登录、个人资料集成、支持One Tap登录 需要Google账户、依赖第三方
Facebook认证 社交集成、用户基础广泛 隐私问题、采用率低于Google
GitHub认证 适合开发者工具、与GitHub API集成 仅限于开发者、不适合通用应用
邮箱/密码 不依赖第三方服务 需要额外的密码管理安全措施

结论

恭喜!您已成功在React.js和Express.js应用中实现了使用最新身份服务SDK的Google登录功能。此设置支持基于Web的登录和One Tap登录,增强了用户便利性。

React Google认证、Google OAuth React和react-oauth/google的组合提供了一个强大的认证框架,既简化了集成又保持了可扩展性。

源代码可在以下位置获取:服务器端客户端

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计