如何在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的组合提供了一个强大的认证框架,既简化了集成又保持了可扩展性。
源代码可在以下位置获取:服务器端和客户端。