React Firebase 身份验证终极指南
身份验证是每个应用程序最重要的安全措施之一。
在本文中,我们将讨论身份验证、身份验证类型,比较身份验证和授权,并演示如何将Firebase Authentication和Back4app Authentication与React 应用程序集成。
Contents
什么是身份验证?
身份验证是验证某人或某事是否与其声称的身份相符的过程。几乎每个应用程序都使用某种形式的身份验证来确保对应用程序或其数据的访问安全。
最简单的身份验证形式是基于密码的身份验证。这种身份验证系统首先由用户输入用户名和密码。用户输入凭据后,后台会将其与数据库中的记录进行比较,如果凭据正确,则允许用户访问系统。访问权限通常以会话令牌的形式授予。
现代身份验证系统大致是这样的:
认证与授权
认证是验证用户身份的过程,而授权则是验证用户访问权限的过程。
例如,当您登录网上银行系统时,系统将首先通过验证您的登录凭据对您进行身份验证。通过身份验证后,系统将根据您的账户权限确定您有权进行的操作,如是否允许您转账或查看账户对账单。
认证类型
常见的身份验证类型包括
- 基于密码的身份验证是最常见的身份验证类型。用户通过提供用户名和密码来访问系统或资源。
- 单点登录(SSO)允许用户使用一套凭证登录多个网站和服务。Google账户就是一个例子。创建 Google 账户后,您就可以访问 Gmail、YouTube、AdSense、Firebase 等。
- 社交认证是单点登录的一种形式,它使用Google、Facebook 或 Twitter 等社交网络服务中的现有信息来创建账户。
- 基于令牌的身份验证让用户只需输入一次凭据,就能换取一个由随机字符组成的独特加密字符串。
- 生物识别身份验证依赖于个人独特的生物特征。生物识别身份验证的类型包括面部识别、指纹扫描仪、扬声器识别和虹膜扫描仪。
- 基于证书的身份验证使用数字证书验证个人、组织或设备的身份,以建立安全连接,通过互联网交换信息。
如何设置身份验证?
在本部分教程中,我们将首先构建一个虚拟的React身份验证应用程序,然后了解如何将其与Firebase Authentication和 Back4app (Parse) Authentication 集成。
我们将使用以下工具:
先决条件
- 使用 JavaScript ES6 的经验
- 充分了解 React(JSX、钩子)
- 有 React Router 和 Material UI 经验者优先考虑
在深入研究代码之前,我们先来看看这两种身份验证系统的区别。
Firebase 身份验证 vs Back4app 身份验证
Firebase和Back4app都为移动和网络应用程序提供了出色的用户身份验证系统。它们都易于使用,并具有社交登录、电子邮件验证系统、密码重置系统等多种功能。
它们之间最大的区别在于,Back4app 是基于开源软件构建的,而 Firebase 使用的是 Google 的专有软件。此外,Back4app 的价格往往比 Firebase 便宜。
我建议你同时试用这两种身份验证系统,然后再决定你更喜欢哪一种。
如果您想了解 Back4app 和 Firebase 的区别,请参阅Back4app vs Firebase。
项目设置
我们已经完成了理论学习。是时候开始编码了!
创建 React 应用程序
让我们先创建一个新的 React 应用程序。
创建 React 应用程序的最简单方法是通过创建 React 应用程序:
$ npx create-react-app react-firebase-auth
$ cd react-firebase-auth
这将创建一个名为react-firebase-auth 的
新 React 应用程序,并更改工作目录。
接下来,启动项目:
$ npm start
最后,打开http://localhost:3000/查看网络应用。
Material UI
为了简化用户界面的构建过程,我们将使用Material UI— 一个实现了 Google Material Design 的开源 React 组件库。该组件库包含大量开箱即用的预构建组件。
你可以随意将 Material UI 换成不同的 UI 框架,如React Bootstrap或Ant Design。
要在项目中添加 Material UI,请运行
$ npm install @mui/material @emotion/react @emotion/styled
Material UI 默认使用 Roboto 字体。让我们用
$ npm install @fontsource/roboto
接下来,导航至index.js,并添加以下导入:
// src/index.js
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
React 路由器
我们的网络应用程序将有以下端点:
/login
— 显示允许用户登录的登录表单。/register
— 显示允许用户注册的注册表单。/user
— 显示用户信息(电子邮件
、accessToken
等)。
由于我们要构建的是单页面应用程序(Single Page Application,SPA),因此需要一种方法来实现客户端路由。使用 React 的最简单方法就是使用react-router-dom。
$ npm install react-router-dom
接下来,在src中为所有视图组件创建一个名为routes的目录。然后在其中添加以下三个文件:
// src/routes/login.jsx
export default function Login() {
return (
<h1>Login</h1>
)
}
// src/routes/register.jsx
export default function Register() {
return (
<h1>Register</h1>
)
}
// src/routes/user.jsx
export default function User() {
return (
<h1>User</h1>
)
}
创建一个BrowserRouter
,并在index.js中像这样注册:
// src/index.js
const router = createBrowserRouter([
{
path: "/",
element: <Navigate to="login"/>,
},
{
path: "/login",
element: <Login/>,
},
{
path: "/register",
element: <Register/>,
},
{
path: "/user",
element: <User/>,
},
]);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}/>
</React.StrictMode>
);
不要忘记在文件顶部添加所有必要的导入:
import {createBrowserRouter, Navigate, RouterProvider} from "react-router-dom";
import Login from "./routes/login";
import Register from "./routes/register";
import User from "./routes/user";
要处理 “未找到 (404) “错误,请在src目录中创建一个名为error-page.jsx的新组件,内容如下:
// src/error-page.jsx
import {useRouteError} from "react-router-dom";
import {Container, Typography} from "@mui/material";
export default function ErrorPage() {
const error = useRouteError();
console.error(error);
return (
<Container maxWidth="xs" sx={{mt: 2}}>
<Typography variant="h5" component="h1" gutterBottom>
Oops!
</Typography>
<Typography variant="p" component="p" gutterBottom>
Sorry, an unexpected error has occurred.
</Typography>
<Typography variant="p" component="p" gutterBottom>
<i>{error.statusText || error.message}</i>
</Typography>
</Container>
);
}
然后导航到index.jsx,在索引路由中添加errorElement
,如下所示:
// src/index.js
const router = createBrowserRouter([
{
path: "/",
element: <Navigate to="login"/>,
errorElement: <ErrorPage/>, // new
},
// ...
]);
再次确保添加所需的导入:
import ErrorPage from "./error-page";
此时,您的目录结构应该如下所示:
react-firebase-auth
├── README.md
├── package.json
├── package-lock.json
├── public
│ └── <public files>
└── src
├── App.css
├── App.js
├── App.test.js
├── error-page.jsx
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── routes
│ ├── login.jsx
│ ├── register.jsx
│ └── user.jsx
└── setupTests.js
让我们启动应用程序,测试是否一切正常。运行:
$ npm start
打开您最喜欢的网络浏览器,导航至http://localhost:3000/。这将把您重定向到登录屏幕。然后导航至http://localhost:3000/register,您应该会看到注册屏幕。
形式
我们要做的最后一件事就是实现登录和注册表单。
为此,请导航到路由目录,然后像这样更改login.jsx:
// src/routes/login.jsx
import {Alert, Box, Button, Container, Link, TextField, Typography} from "@mui/material";
import {useNavigate} from "react-router-dom";
import {useState} from "react";
export default function Login() {
const navigate = useNavigate();
const [error, setError] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onSubmit = async (event) => {
event.preventDefault();
// validate the inputs
if (!email || !password) {
setError("Please enter your username and password.");
return;
}
// clear the errors
setError("");
// TODO: send the login request
console.log("Logging in...");
}
return (
<Container maxWidth="xs" sx={{mt: 2}}>
<Typography variant="h5" component="h1" gutterBottom textAlign="center">
Login
</Typography>
{error && <Alert severity="error" sx={{my: 2}}>{error}</Alert>}
<Box component="form" onSubmit={onSubmit}>
<TextField
label="Email"
variant="outlined"
autoComplete="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
sx={{mt: 1}}
fullWidth
/>
<TextField
label="Password"
variant="outlined"
type="password"
autoComplete="new-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
sx={{mt: 3}}
fullWidth
/>
<Button variant="contained" type="submit" sx={{mt: 3}} fullWidth>Login</Button>
<Box sx={{mt: 2}}>
Don't have an account yet? <Link href="/register">Register</Link>
</Box>
</Box>
</Container>
)
}
- 我们使用 Material UI 的组件来构建布局,包括表单。
- 状态
(电子邮件
和密码
)通过 ReactuseState()
钩子处理。 - 表单提交会调用
onSubmit()
验证数据并显示潜在错误。
接下来,从 GitHub 获取源代码,替换其他两个路由的内容:
再次运行服务器并导航到网络应用程序。您的视图应该如下所示:
清理
删除以下由Create React App创建但不再需要的文件:
- src/App.css
- src/App.js
- src/App.test.js
- src/logo.svg
确保从index.js等文件中移除任何可能的导入。
很好,我们的虚拟身份验证网络应用程序现在完成了。在接下来的章节中,我们将通过添加 Firebase 身份验证和 Back4app 身份验证对其进行升级。
从back4app-react-firebase-auth软件仓库获取这部分内容的源代码。
React Firebase 身份验证
在本节教程中,我们将了解如何将 Firebase 身份验证与 React 项目集成。首先,我们将使用在 “项目设置 “部分创建的 React 应用程序。
在学习本教程的过程中,请使用自己的 React 项目来检查您的理解。
创建项目和应用程序
以下步骤需要您拥有一个Firebase 账户。如果您还没有账户,请使用 Google 账户注册。
要使用 Firebase,我们首先需要创建一个项目。要创建项目,请登录Firebase 控制台并点击 “创建项目”:
给它起一个自定义名称,我的名称是react-firebase-auth
。
接受所有条款和条件,然后按 “继续”。
Firebase 会花一些时间准备项目所需的一切。一旦完成,你将被重定向到 Firebase 项目控制面板。
接下来,创建一个新的 Firebase 应用程序。选择 “Web”,因为我们使用 React 作为前端:
给它起一个自定义名称–或者重复使用你的项目名称。您不必启用 Firebase Hosting,因为我们不会使用它。
接下来,点击 “注册应用程序”:
然后选择 “npm”,并记下你的 Firebase SDK 配置。最后,点击 “继续到控制台 “按钮:
启用身份验证
在深入学习代码之前,我们必须启用身份验证。
在 Firebase 控制台左侧选择 “构建”,然后选择 “身份验证”。重定向后,点击 “开始”:
Firebase 提供多种登录方法。在本教程中,我们将演示如何使用电子邮件和密码验证,因此这是你唯一需要启用的方法:
Firebase SDK
要在 React 项目中使用 Firebase,首先必须安装其软件开发工具包(SDK)。
通过 npm 安装:
$ npm install firebase
接下来,在src文件夹中新建一个名为firebase.js的文件,内容如下:
// src/firebase.js
import { initializeApp } from "firebase/app";
import {createUserWithEmailAndPassword, signInWithEmailAndPassword, getAuth} from "firebase/auth";
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyC4m7VHfM8hy_VUUAlpFCSK3AfrRX4bkQ0",
authDomain: "react-firebase-auth-d4e6b.firebaseapp.com",
projectId: "react-firebase-auth-d4e6b",
storageBucket: "react-firebase-auth-d4e6b.appspot.com",
messagingSenderId: "1084832623816",
appId: "1:1084832623816:web:a526bb5b9beff5e26e89fd",
measurementId: "G-1DXS0RGXPT"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
确保将
firebaseConfig
替换为上一步中的配置。
Firebase SDK 提供不同的验证方法,例如
createUserWithEmailAndPassword(email: string, password: string)
signInWithEmailAndPassword(email: string, password: string)
要使用这些方法,让我们在firebase.js文件底部创建两个封装函数:
// firebase.js
// ...
export const createUser = async (email, password) => {
return createUserWithEmailAndPassword(getAuth(app), email, password);
}
export const signInUser = async (email, password) => {
return signInWithEmailAndPassword(getAuth(app), email, password);
}
别忘了添加导入:
import {createUserWithEmailAndPassword, signInWithEmailAndPassword, getAuth} from "firebase/auth";
这些功能不言自明:
createUser
创建 Firebase 用户(并返回包含用户信息的响应)signInUser
登录现有 Firebase 用户(并返回包含用户信息的响应)
会话持久性
Firebase 不会像 Parse 那样在客户端设备上进行会话持久化处理。
因此,我们必须使用Window.sessionStorage
。当用户登录时,我们必须存储他们的访问令牌
,并在每次进行验证请求时检索它。
为了让事情变得简单一些,我们将创建一个辅助类。
在src目录中新建一个名为session.js的文件,内容如下:
// src/storage/session.js
export const startSession = (user) => {
sessionStorage.setItem("email", user.email);
sessionStorage.setItem("accessToken", user.accessToken);
}
export const getSession = () => {
return {
email: sessionStorage.getItem("email"),
accessToken: sessionStorage.getItem("accessToken"),
}
}
export const endSession = () => {
sessionStorage.clear();
}
export const isLoggedIn = () => {
return getSession().accessToken;
}
意见
我们要做的最后一件事就是修改视图,以便利用我们创建的函数,并通过session.js 中的辅助函数设置会话。
打开login.jsx,像这样更改onSubmit()
TODO:
// src/routes/login.jsx
import {signInUser} from "../firebase";
import {startSession} from "../session";
const onSubmit = async (event) => {
// ...
try {
let loginResponse = await signInUser(email, password);
startSession(loginResponse.user);
navigate("/user");
} catch (error) {
console.error(error.message);
setError(error.message);
}
}
这段代码要么登录用户并导航到/user
,要么显示错误。
接下来,像这样修改register.jsx onSubmit()
TODO:
// src/routes/register.jsx
import {createUser} from "../firebase";
import {startSession} from "../session";
const onSubmit = async (event) => {
// ...
try {
let registerResponse = await createUser(email, password);
startSession(registerResponse.user);
navigate("/user");
} catch (error) {
console.error(error.message);
setError(error.message);
}
}
该代码要么创建 Firebase 用户,要么显示错误。可能出现的错误包括
- 用户名已被注册。
- 密码不够强。
最后,修改user.jsx,使用useEffect()
钩子获取用户数据,并让onLogout()
销毁会话:
// src/routes/user.jsx
import {endSession, getSession, isLoggedIn} from "../session";
useEffect(() => {
if (!isLoggedIn()) {
navigate("/login");
}
let session = getSession();
setEmail(session.email);
console.log("Your access token is: " + session.accessToken);
}, [navigate]);
const onLogout = () => {
endSession();
navigate("/login");
}
测试
让我们测试一下网络应用程序,看看是否一切正常。
使用npm start
启动开发服务器,打开您喜欢的网络浏览器,并导航至http://localhost:3000/register。输入电子邮件、密码,然后点击 “注册”:
注册后,您将被重定向到/user
,在那里您可以看到您的账户信息。
要查看访问令牌
,请打开浏览器的开发者控制台:
Your access token is: 819423a698f9ea9ba3577f20993cb0da98a79ea22ce5d6550b65b69fb36fd438
最后,导航到 Firebase 项目仪表板,检查是否创建了新用户:
从back4app-react-firebase-authrepo 中获取此方法的最终源代码。
React Back4app 身份验证
在本节教程中,我们将了解如何将 Back4app 身份验证与 React 项目集成。首先,我们将使用在 “项目设置 “部分创建的 React 应用程序。
在学习本教程的过程中,请使用自己的 React 项目来检查您的理解。
创建应用程序
要使用 Back4app,我们首先需要创建一个应用程序。登录仪表板后,您将看到应用程序列表。点击 “创建新应用程序 “创建新应用程序。
自定义名称,选择数据库,然后点击 “继续”。
Back4app 会花一些时间准备应用程序所需的一切,如数据库、应用层、扩展、备份和安全。
应用程序准备就绪后,您将被重定向到应用程序的控制面板。
应用程序密钥
要将您的 React 项目与 Back4app 连接,您需要获取应用程序密钥。要获取应用密钥,请选择侧边栏上的 “应用设置”,然后选择 “安全与密钥”。
注意 “应用程序 ID “和 “JavaScript 密钥”。
Parse SDK
您可能已经知道,Back4app 是基于 Parse 开发的。Parse 是一个用于构建应用程序后端的开源框架。它可以帮助开发人员加快应用程序的开发速度,减少构建应用程序所需的总工作量。
要了解有关 Parse 的更多信息,请参阅《什么是 Parse?
要设置 Back4app 身份验证,我们首先需要安装 Parse SDK。通过 npm 安装:
$ npm install parse
接下来,在src文件夹中新建一个名为parse.js的文件,内容如下:
// src/parse.js
import Parse from "parse/dist/parse";
// Initialize Parse
const PARSE_APPLICATION_ID = '<your_parse_application_id>';
const PARSE_HOST_URL = 'https://parseapi.back4app.com/';
const PARSE_JAVASCRIPT_KEY = '<your_parse_javascript_key>';
Parse.initialize(PARSE_APPLICATION_ID, PARSE_JAVASCRIPT_KEY);
Parse.serverURL = PARSE_HOST_URL;
确保将
PARSE_APPLICATION_ID
和PARSE_JAVASCRIPT_KEY
替换为上一步中的键值。
Parse 提供了一个名为Parse.User
的专门类,它能自动处理用户账户管理所需的大部分功能。Parse.User
是ParseObject
的一个子类,它提供了额外的辅助方法,如signUp
()
、current()
、getUsername()
等。
此外,Parse SDK 还能处理本地存储会话。例如,Parse.User.logIn()
会将用户信息存储在本地
存储中,而Parse.User.signOut()
则会清除本地存储。
让我们添加几个封装函数,进一步简化 Parse 身份验证系统的工作。
在parse.js 的底部添加以下内容:
// src/parse.js
// ...
export const doUserRegistration = async (username, password) => {
return Parse.User.signUp(username, password);
};
export const doUserLogIn = async (username, password) => {
return Parse.User.logIn(username, password);
};
export const isLoggedIn = async () => {
return Parse.User.current() != null;
}
export const getUser = async () => {
return Parse.User.current();
}
export const logOut = async () => {
return Parse.User.logOut();
}
doUserRegistration()
创建 Parse 用户(使用电子邮件和密码)doUserLogIn()
尝试使用提供的凭据登录用户isLoggedIn()
检查localStorage
是否包含会话信息getUser()
会从本地存储
返回已登录用户的信息logOut()
清除本地存储
,从而注销用户
所有函数都是异步的,并返回承诺。
意见
让我们利用上一步创建的函数。
打开login.jsx,像这样更改onSubmit()
TODO:
// src/routes/login.jsx
import {doUserLogIn} from "../parse";
const onSubmit = async (event) => {
// ...
try {
let user = await doUserLogIn(email, password);
navigate("/user");
} catch (error) {
console.error(error.message);
setError(error.message);
}
}
这段代码要么登录用户并导航到/user
,要么显示错误。
接下来,像这样修改register.jsx onSubmit()
TODO:
// src/routes/register.jsx
import {doUserRegistration} from "../parse";
const onSubmit = async (event) => {
// ...
let username = email.split("@")[0];
try {
let user = await doUserRegistration(username, password);
user.setEmail(email).save();
navigate("/user");
} catch (error) {
console.error(error.message);
setError(error.message);
}
}
请记住,由于我们使用的是电子邮件/密码注册表单,因此必须从电子邮件中提取用户的用户名。用户名是
@
前面的部分。更好的解决方案是创建一个用户名/密码注册表。
该代码要么创建 Parse 用户,要么显示错误。可能出现的错误包括
- 用户名已被注册。
- 密码不够强。
最后,修改user.jsx,使用useEffect()
钩子获取用户数据,并让onLogout()
调用注销函数:
// src/routes/user.jsx
import {getUser, isLoggedIn, logOut} from "../parse";
useEffect(() => {
(async () => {
let loggedIn = await isLoggedIn();
if (!loggedIn) {
navigate("/login");
}
let user = await getUser();
setEmail(user.getEmail());
console.log("Your session token is: " + user.getSessionToken());
})();
}, [navigate]);
const onLogout = async () => {
await logOut();
navigate("/login");
}
很好,就是这样!
测试
让我们确保我们的身份验证系统按预期运行。
使用npm start
启动开发服务器,打开您喜欢的网络浏览器,并导航至http://localhost:3000/register。输入电子邮件、密码,然后点击 “注册”:
注册后,您将被重定向到/user
,在那里您可以看到您的账户信息。
要查看会话令牌
,请打开浏览器的开发者控制台:
Your session token is: r:90343c307e7bb088e60c348acd8090d1
最后,导航至 Back4app 应用程序控制面板,确保已创建用户:
从back4app-react-firebase-authrepo 中获取此方法的最终源代码。
结论
身份验证和授权是现代应用程序最重要的安全措施。身份验证是验证用户身份的过程,而授权则是验证用户访问权限的过程。
Firebase 和 Back4app 都提供了出色的用户身份验证系统。它们各有利弊,在启动项目时应加以考虑。
更多阅读
常见问题
什么是身份验证?
身份验证是验证用户或设备身份的过程。这是一种安全措施,旨在确保只有授权的用户或设备才能访问系统、网络或资源。
身份验证有哪些类型?
– 用户名和密码
– 单点登录(SSO)
– 社交身份验证
– 基于令牌的身份验证
– 生物识别身份验证
– 基于证书的身份验证
如何使用 Firebase 设置身份验证?
1. 使用 Google 账户登录 Firebase
2. 创建一个 Firebase 项目和应用
3. 启用 Firebase 身份验证
4. 安装并初始化 Firebase SDK
5. 在你的视图中使用 Firebase SDK 的身份验证功能
6. 使用 Window.sessionStorage
来处理会话
如何使用 Back4app 设置身份验证?
1. 在 Back4app 上创建账户
2. 创建一个 Back4app 应用
3. 安装并初始化 Parse SDK
4. 在你的视图中使用 ParseJS 的身份验证功能