React Firebase 身份验证
我们将使用 React 和 Firebase Authentication SDK 构建一个简单的身份验证和安全应用程序。用户将能够创建帐户、登录和退出。我们还将确保某些路由(私有页面)的安全,仅供经过身份验证的用户使用。希望对您有所帮助!
应用程序安装和设置
首先,我们将创建一个应用程序,该应用程序将使用 Facebook 的官方 React 样板create-react-app 进行引导。
运行
npm i -g create-react-app
以将其安装在本地计算机上
# Creating an App
create-react-app react-firebase-auth
# Change directory
cd react-firebase-auth
# Additional packages to install
yarn add firebase react-router-dom react-props
初始项目结构现已生成,所有依赖项也已成功安装。让我们来看看项目层次结构及其文件夹结构,如下所示:
# Make sub-directories under src/ path
mkdir -p src/{components,firebase,shared}
# Move App component under src/components
mv src/App.js src/components
# Create desired files to work with
touch src/components/{AppProvider,Navbar,FlashMessage,Login,Signup}.js
touch src/firebase/{firebase,index,auth,config}.js
touch src/shared/Form.js
我们需要通过命令行列出所有创建的文件和子目录,以确保一切顺利cd src/ && ls * -r
# terminal
react-firebase-auth % cd src/ && ls * -r
components:
App.js AppProvider.js FlashMessage.js Login.js Navbar.js Signup.js
firebase:
auth.js config.js firebase.js index.js
shared:
Form.js
Firebase
我们不会深入研究 Firebase 本身。
如果您不熟悉 Firebase,请务必查看其指南,
了解如何将 Firebase 添加到您的 JavaScript 项目。
Firebase 配置
// src/firebase/config.js
const devConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "AUTH_DOMAIN",
databaseURL: "DATABASE_URL",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "MESSAGING_SENDER_ID"
};
const prodConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "AUTH_DOMAIN",
databaseURL: "DATABASE_URL",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "MESSAGING_SENDER_ID"
};
export {
devConfig,
prodConfig
}
配置细分
- 用于开发环境的devConfig
- prodConfig用于生产环境
📌 为您的项目准备一个包含预定义设置(如上所示)的配置模板文件始终是明智之举,这样可以避免将敏感数据推送到存储库。您或您团队中的任何成员都可以稍后使用适当的文件扩展名复制此模板。示例(基于此文章):创建一个文件,firebase.config
打开您的文件.gitignore
,然后添加 并运行该配置模板的副本。app/config.js
cp app/firebase.config app/config.js
Firebase 初始化
// src/firebase/firebase.js
import * as firebase from 'firebase';
import { devConfig } from './config';
!firebase.apps.length && firebase.initializeApp(devConfig);
const auth = firebase.auth();
export {
auth
}
授权模块
// src/firebase/auth.js
import { auth } from './firebase';
/**
* Create user session
* @param {string} action - createUser, signIn
* @param {string} email
* @param {string} password
*/
const userSession = (action, email, password) => auth[`${action}WithEmailAndPassword`](email, password);
/**
* Destroy current user session
*/
const logout = () => auth.signOut();
export {
userSession,
logout
}
授权模块细分
- userSession 是一个接受三个参数的函数,action:决定用户是否创建帐户或登录,电子邮件和密码
- logout会销毁当前用户会话并将用户注销系统
Firebase 模块
// src/firebase/index.js
import * as auth from './auth';
import * as firebase from './firebase';
export {
auth,
firebase
}
成分
提供程序组件
// src/components/AppProvider.js
import React, {
Component,
createContext
} from 'react';
import { firebase } from '../firebase';
export const {
Provider,
Consumer
} = createContext();
class AppProvider extends Component {
state = {
currentUser: AppProvider.defaultProps.currentUser,
message: AppProvider.defaultProps.message
}
componentDidMount() {
firebase.auth.onAuthStateChanged(user => user && this.setState({
currentUser: user
}))
}
render() {
return (
<Provider value={{
state: this.state,
destroySession: () => this.setState({
currentUser: AppProvider.defaultProps.currentUser
}),
setMessage: message => this.setState({ message }),
clearMessage: () => this.setState({
message: AppProvider.defaultProps.message
})
}}>
{this.props.children}
</Provider>
)
}
}
AppProvider.defaultProps = {
currentUser: null,
message: null
}
export default AppProvider;
AppProvider 细分
AppProvider 是一个 React 组件,它提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props,并允许消费者订阅上下文变化。
- componentDidMount在组件安装后,我们检查用户是否存在。
导航栏组件
// src/components/Navbar.js
import React from 'react';
import {
Link,
withRouter
} from 'react-router-dom';
import { auth } from '../firebase';
import { Consumer } from './AppProvider';
const Navbar = props => {
const handleLogout = context => {
auth.logout();
context.destroySession();
props.history.push('/signedOut');
};
return <Consumer>
{({ state, ...context }) => (
state.currentUser ?
<ul>
<li><Link to="/dashboard">Dashboard</Link></li>
<li><a onClick={() => handleLogout(context)}>Logout</a></li>
</ul>
:
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/login">Login</Link></li>
<li><Link to="/signup">Create Account</Link></li>
</ul>
)}
</Consumer>
};
export default withRouter(Navbar);
导航栏细分
Navbar 组件处理 UI 逻辑如下:
- 如果系统登录用户,那么我们会显示仪表板(受保护的页面)和注销按钮,该按钮会将用户踢出并重定向到
/signedOut
页面。 - 如果没有找到用户,那么我们会显示主页、登录和创建和帐户链接。
FlashMessage 组件
// src/components/FlashMessage.js
import React from 'react';
import { Consumer } from '../components/AppProvider';
const FlashMessage = () => <Consumer>
{({ state, ...context }) => state.message && <small className="flash-message">
{state.message}
<button type="button" onClick={() => context.clearMessage()}>Ok</button>
</small>}
</Consumer>;
export default FlashMessage;
FlashMessage 故障
FlashMessage 是一个由Consumer包装的无状态组件,用于订阅上下文变化。当出现问题(例如表单验证、服务器错误等)时,它会显示出来。FlashMessage 有一个“确定”按钮,可以清除消息并关闭/隐藏它。
表单组件
// src/shared/Form.js
import React, {
Component,
createRef
} from 'react';
import PropTypes from 'prop-types';
import { auth } from '../firebase';
class Form extends Component {
constructor(props) {
super(props);
this.email = createRef();
this.password = createRef();
this.handleSuccess = this.handleSuccess.bind(this);
this.handleErrors = this.handleErrors.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSuccess() {
this.resetForm();
this.props.onSuccess && this.props.onSuccess();
}
handleErrors(reason) {
this.props.onError && this.props.onError(reason);
}
handleSubmit(event) {
event.preventDefault();
const {
email,
password,
props: { action }
} = this;
auth.userSession(
action,
email.current.value,
password.current.value
).then(this.handleSuccess).catch(this.handleErrors);
}
resetForm() {
if (!this.email.current || !this.password.current) { return }
const { email, password } = Form.defaultProps;
this.email.current.value = email;
this.password.current.value = password;
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<h1>{this.props.title}</h1>
<input
name="name"
type="email"
ref={this.email}
/>
<input
name="password"
type="password"
autoComplete="none"
ref={this.password}
/>
<button type="submit">Submit</button>
</form>
)
}
}
Form.propTypes = {
title: PropTypes.string.isRequired,
action: PropTypes.string.isRequired,
onSuccess: PropTypes.func,
onError: PropTypes.func
}
Form.defaultProps = {
errors: '',
email: '',
password: ''
}
export default Form;
表格细分
- 电子邮件和密码都会创建一个 ref
createRef()
,我们稍后可以通过 ref 属性将其附加到 React 元素。 - handleSuccess方法执行
resetForm
方法,并从给定的道具中回调函数(如果找到的话!)。 - handleErrors方法根据给定的 props(如果找到的话!)执行回调函数并给出原因。
- handleSubmit方法阻止默认
form
行为,并执行auth.userSession
创建帐户或登录用户的操作。
登录组件
// src/components/Login.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import Form from '../shared/Form';
import { Consumer } from './AppProvider';
const Login = props => <Consumer>
{({ state, ...context }) => (
<Form
action="signIn"
title="Login"
onSuccess={() => props.history.push('/dashboard')}
onError={({ message }) => context.setMessage(`Login failed: ${message}`)}
/>
)}
</Consumer>;
export default withRouter(Login);
登录故障
登录组件是一个由Consumer包装的无状态组件,用于订阅上下文变化。如果登录成功,用户将被重定向到一个受保护的页面(仪表盘),否则会弹出错误消息。
注册组件
// src/components/Signup.js
import React from 'react';
import { withRouter } from 'react-router-dom';
import Form from '../shared/Form';
import { auth } from '../firebase';
import { Consumer } from './AppProvider';
const Signup = props => <Consumer>
{({ state, ...context }) => (
<Form
action="createUser"
title="Create account"
onSuccess={() => auth.logout().then(() => {
context.destroySession();
context.clearMessage();
props.history.push('/accountCreated');
})}
onError={({ message }) => context.setMessage(`Error occured: ${message}`)}
/>
)}
</Consumer>;
export default withRouter(Signup);
注册细目
Signup 是一个由Consumer包装的无状态组件,用于订阅上下文变化。默认情况下,Firebase 会在帐户创建成功后自动登录用户。我修改了此实现,让用户在帐户创建后手动登录。一旦onSuccess
回调触发,我们会注销用户,并重定向到/accountCreated
包含自定义消息和“继续前往仪表板”链接的页面,引导用户登录。如果帐户创建失败,则会弹出错误消息。
应用程序组件(容器)
// src/components/App.js
import React, {
Component,
Fragment
} from 'react';
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
import AppProvider, {
Consumer
} from './AppProvider';
import Login from './Login';
import Signup from './Signup';
import Navbar from '../shared/Navbar';
import FlashMessage from '../shared/FlashMessage';
class App extends Component {
render() {
return (
<AppProvider>
<Router>
<Fragment>
<Navbar />
<FlashMessage />
<Route exact path="/" component={() =>
<h1 className="content">Welcome, Home!</h1>} />
<Route exact path="/login" component={() => <Login />} />
<Route exact path="/signup" component={() => <Signup />} />
<Router exact path="/dashboard" component={() => <Consumer>
{
({ state }) => state.currentUser ?
<h1 className="content">Protected dashboard!</h1> :
<div className="content">
<h1>Access denied.</h1>
<p>You are not authorized to access this page.</p>
</div>
}
</Consumer>} />
<Route exact path="/signedOut" component={() =>
<h1 className="content">You're now signed out.</h1>} />
<Route exact path="/accountCreated" component={() =>
<h1 className="content">Account created. <Link to="/login">
Proceed to Dashboard</Link></h1>} />
</Fragment>
</Router>
</AppProvider>
);
}
}
export default App;
应用程序(容器)细分
一切都非常简单!导航组件 Router 被 AppProvider 包装,用于在组件树中传递数据。/dashboard
路由组件包含受保护的内容(页面),仅供经过身份验证的用户使用。如果没有用户登录,我们会显示“访问被拒绝”消息,而不是显示我们私有的内容/页面。
演示
点击此处查看demo-gif
欢迎反馈如果您有任何建议或更正,请随时给我留言/评论。
文章来源:https://dev.to/mahmoudelmahdi/react-firebase-authentication-3bik