在React与Express应用中配置新版Google身份验证的完整指南

本文详细介绍了如何在React.js和Express.js应用中集成新版Google身份验证,包括生成客户端ID、配置前后端、实现登录/注册流程,以及对比其他认证方法的优缺点。

在React与Express应用中配置新版Google身份验证

本文作者:Onuorah Bonaventure Chukwudi
发布于:JavaScript·Web·2025年1月3日·更新:2025年1月3日

文章概述

本文将指导您如何在React.js和Express.js应用中配置新版Google“使用Google登录”按钮。随着Google身份服务SDK的更新,集成Google登录功能变得更加简单和强大。新方法引入了账户选择时的个人资料图片预览、一键登录(One Tap)以及增强的令牌管理功能。由于旧的“使用Google登录”JavaScript库已被弃用,现在转向这种更新方法对新项目和现有项目都至关重要。

主要内容

生成Google客户端ID和密钥

首先,需要在Google云控制台中生成Google客户端ID和密钥。这些凭证对于在应用中设置安全身份验证至关重要。

步骤:

  1. 导航到Google控制台。
  2. 创建新项目,输入项目名称(如connect-google-auth-2024)。
  3. 配置OAuth同意屏幕,提供应用名称、支持邮箱和徽标等详细信息。
  4. 创建OAuth 2.0客户端ID,设置应用类型为“Web应用”,并添加授权重定向URI(如http://localhosthttp://localhost:3000)。
  5. 下载生成的客户端ID和密钥。

设置React应用

使用Create React App或类似现代工具初始化React.js应用。安装@react-oauth/google包以利用Google身份服务SDK。

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

设置Express服务器

在根目录创建server文件夹,初始化package.json,并安装必要的包:

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

配置服务器脚本和基本服务器代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// package.json
{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
}

// server.js
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应用

现代应用不再需要手动添加Google脚本。改为从@react-oauth/google包导入GoogleLogin组件以实现更清晰的集成。

1
2
<!-- index.html -->
<script src="https://accounts.google.com/gsi/client" async defer></script>

配置客户端路由

使用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
24
25
26
27
28
// App.js
import React, { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { Landing, Login, Signup, Home } from "./screens";

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>
  );
};

export default App;

创建登录和注册页面

**登录页面(Login.jsx)和注册页面(Signup.jsx)**使用Google身份服务SDK渲染登录按钮,并处理认证回调。

 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
// Signup.jsx
import React, { useEffect } from "react";
import { Link } from "react-router-dom";
import useFetch from "../hooks/useFetch";

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

  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("signUpDiv"), {
        theme: "filled_black",
        text: "continue_with",
        shape: "pill",
      });
    }
  }, [handleGoogle]);

  return (
    <>
      <nav><Link to="/">Go Back</Link></nav>
      <header><h1>Register to continue</h1></header>
      <main>
        {error && <p style={{ color: "red" }}>{error}</p>}
        {loading ? <div>Loading....</div> : <div id="signUpDiv" data-text="signup_with"></div>}
      </main>
    </>
  );
};

export default SignUp;

创建useFetch钩子

useFetch钩子处理Google认证响应,并向服务器发送请求以验证凭证和创建用户会话。

 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
// useFetch.jsx
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) => 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))
      .finally(() => setLoading(false));
  };

  return { loading, error, handleGoogle };
};

export default useFetch;

创建服务器端路由

在服务器端,创建用于验证Google令牌和处册/登录请求的路由。

 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// server.js
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" };
  }
}

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." });
  }
});

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 });
  }
});

创建主页和登出功能

主页(Home.jsx)显示用户信息并提供登出功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Home.jsx
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>
      <button onClick={logout} style={{ color: "red", border: "1px solid gray", backgroundColor: "white", padding: "0.5rem 1rem", cursor: "pointer" }}>
        Logout
      </button>
    </div>
  );
};

export default Home;

与其他认证方法的比较

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

结论

恭喜!您已成功在React.js和Express.js应用中实现了使用最新身份服务SDK的Google登录。此设置支持基于Web的登录和一键登录,以增强用户便利性。React Google Auth、Google OAuth React和react-oauth/google的组合提供了一个强大的身份验证框架,在简化集成的同时保持了可扩展性。不要忘记全面测试您的应用,以确保安全无缝的身份验证。

源代码可在此处获取。

常见问题解答(FAQs)

如何在带有Express后端的React应用中设置React Google登录? 首先在Google开发者控制台中创建凭证以获取Google客户端ID。使用此ID配置OAuth同意屏幕,确保设置了授权的JavaScript来源和重定向URI。在服务器上安装必要的npm包(如express、cors和google-auth-library)。在React应用中,使用react-google-login组件进行前端集成。

如何在React和Express应用中安全获取访问令牌? 在Google登录React实现中,使用react-oauth/google包,该包利用Google的OAuth 2.0协议,确保安全处理身份验证令牌而不暴露敏感数据。

如何在React应用中实现Google登录的登出功能? 提供一个登出按钮,点击时调用函数从本地存储中移除用户详细信息,并从服务器清除会话。这应使会话无效,并将用户重定向到登录屏幕,确保用户完全登出。

如何使用Google登录在React和Express应用中刷新令牌? 使用Google的OAuth 2.0服务器提供的刷新令牌在当前令牌过期时获取新的访问令牌。在Express后端设置中间件,检查令牌的有效性并在需要时刷新它。这使用户无需重复输入凭据即可保持登录状态。

在React Google身份验证中管理用户详细信息的最佳实践是什么? 成功登录后,使用Google返回的ID令牌检索用户详细信息。将这些详细信息存储在本地存储或状态管理解决方案中,以个性化用户体验而不影响安全性。确保敏感数据处理符合最佳实践以保护用户信息。

我可以创建自定义按钮登录到我的React应用吗? 是的,您可以自定义Google登录按钮以更好地适应Web应用的设计。Google通过react-google-login组件提供更改按钮外观的选项,包括大小、文本和颜色。利用这些自定义选项创建独特且无缝的登录流程。

如何配置Google云控制台中的新项目以进行Google身份验证? 转到Google云控制台,点击“创建项目”,输入项目名称,然后导航到API和服务仪表板。从那里启用Google+ API,并通过指定客户端ID、客户端密钥和重定向URI来配置OAuth 2.0凭证。

如何将Google返回的访问令牌用于React和Express应用中的进一步API请求? 使用访问令牌通过将其附加到HTTP授权标头来向Google服务发出授权API请求。这使您的应用能够代表用户访问Google API,从而实现检索个人资料信息或与Google服务交互等功能。

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