React Firebase 身份验证

2025-06-07

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
Enter fullscreen mode Exit fullscreen mode

初始项目结构现已生成,所有依赖项也已成功安装。让我们来看看项目层次结构及其文件夹结构,如下所示:

# 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
Enter fullscreen mode Exit fullscreen mode

我们需要通过命令行列出所有创建的文件和子目录,以确保一切顺利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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

配置细分

  • 用于开发环境的devConfig
  • prodConfig用于生产环境

📌 为您的项目准备一个包含预定义设置(如上所示)的配置模板文件始终是明智之举,这样可以避免将敏感数据推送到存储库。您或您团队中的任何成员都可以稍后使用适当的文件扩展名复制此模板。示例(基于此文章):创建一个文件,firebase.config打开您的文件.gitignore然后添加 并运行该配置模板的副本。app/config.jscp 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
}
Enter fullscreen mode Exit fullscreen mode

授权模块

// 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
}
Enter fullscreen mode Exit fullscreen mode

授权模块细分

  • userSession 是一个接受三个参数的函数,action:决定用户是否创建帐户或登录,电子邮件和密码
  • logout会销毁当前用户会话并将用户注销系统

Firebase 模块

// src/firebase/index.js

import * as auth from './auth';
import * as firebase from './firebase';

export {
  auth,
  firebase
}
Enter fullscreen mode Exit fullscreen mode

成分

提供程序组件

// 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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

导航栏细分

Navbar 组件处理 UI 逻辑如下:

  1. 如果系统登录用户,那么我们会显示仪表板(受保护的页面)和注销按钮,该按钮会将用户踢出并重定向到/signedOut页面。
  2. 如果没有找到用户,那么我们会显示主页、登录和创建和帐户链接。

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

表格细分

  • 电子邮件和密码都会创建一个 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);
Enter fullscreen mode Exit fullscreen mode

登录故障

登录组件是一个由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);
Enter fullscreen mode Exit fullscreen mode

注册细目

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;
Enter fullscreen mode Exit fullscreen mode

应用程序(容器)细分

一切都非常简单!导航组件 Router 被 AppProvider 包装,用于在组件树中传递数据。/dashboard路由组件包含受保护的内容(页面),仅供经过身份验证的用户使用。如果没有用户登录,我们会显示“访问被拒绝”消息,而不是显示我们私有的内容/页面。

演示

点击此处查看demo-gif


欢迎反馈如果您有任何建议或更正,请随时给我留言/评论。

文章来源:https://dev.to/mahmoudelmahdi/react-firebase-authentication-3bik
PREV
我使用 Svelte 创建了一个测验应用程序,现在我无法返回到任何其他框架。
NEXT
在 React 中创建签名板