使用 ReactJS 和 Firebase 进行身份验证

Posted

技术标签:

【中文标题】使用 ReactJS 和 Firebase 进行身份验证【英文标题】:Authentication using ReactJS and Firebase 【发布时间】:2018-12-06 10:42:50 【问题描述】:

我是 Web 开发新手,开始学习 ReactJS。

到目前为止,构建简单的网络应用程序没有问题。

现在我想使用 Firebase 进行身份验证。

我知道如何使用 Firebase 对用户进行身份验证,但我不知道如何设计前端来限制对某些页面的访问。

以前,我使用 php,因为我使用了 session 变量和 include 来包含要显示给用户的 html 部分。

但我不知道如何在 ReactJS 中使用 Firebase。

初始和失败的方法:

我想更新this.state.loggedIn 并使用它来显示组件。但这不是正确的方法,因为我将两个组件都发送给显示,并且我们可以使用 Chrome 中的 React 开发者扩展轻松更改组件的状态。

这是我的主要 index.js 代码:

import React from 'react';
import ReactDOM from 'react-dom';
import Login from './components/Login/Login';
import Home from './components/Home/Home';
import fire from './components/Fire';


class App extends React.Component 

    constructor(props) 
        super(props);
        this.state = 
            loggedIn: false,
        ;

        this.authListener = this.authListener.bind(this);

    

    componentDidMount() 
        this.authListener();
    

    authListener() 
        fire.auth().onAuthStateChanged(user => 
            if (user) 
              // User is signed in.
                console.log(user);
                this.setState(
                    loggedIn: true
                )
             else 
              // No user is signed in.
                this.setState(
                    loggedIn: false
                );
            
        );

    

    render() 

        return (
            <div>
                this.state.loggedIn ? <Home/> : <Login/>
            </div>
        );
    


ReactDOM.render(
    <App/>, document.getElementById('app'));

在 Login.js 中,我正在调用 Firebase 身份验证函数。

只是 sn-p:

fire
    .auth()
    .signInWithEmailAndPassword(this.state.email, this.state.password)
    .catch(function (error) 
          // Handle Errors here.
          var errorCode = error.code;
          var errorMessage = error.message;
          console.log(error);
          // ...
     );

身份验证工作正常,我可以在控制台日志中看到它。

那么,我应该如何在 ReactJS + Firebase 中进行身份验证?(是否可以不使用任何其他包,如 react-routes 等?)

或者我应该为此使用云功能吗?

【问题讨论】:

您可以使用 react-cookies 进行视图访问限制,并在切换视图并进行条件渲染时检查凭据。不漂亮,但会工作 因此,出于安全目的,您希望像在 PHP 中一样将唯一需要的 HTML/JS 发送到客户端。我说的对吗? @SkrewEverything 是的。如果用户已登录,我应该向用户发送与登录时不同的组件。如果用户没有登录,我根本不应该将登录组件发送给用户。 找到解决方案了吗? @unknownymouse 没有。我还在等人来回答。 【参考方案1】:

更新:

您在 html 文件中链接的所有内容都应始终来自您的公共目录。因此,请将捆绑的 .js 文件保存在公共目录中。

我已经为初学者写了一些节省时间的技巧。你可以找到更多关于初学者提示here。


其实很简单。

如果您只想将所需的 html 文件发送到浏览器,则每次需要完全不同的 html 时都必须访问服务器。

你说你使用了 PHP。在 PHP 中,我们使用会话来了解一个人是否已登录。

所以,我们尝试在这里实现它。

首先让我们看看 PHP 中的会话是如何工作的。取自SO Answer

一般情况下:

创建会话时会将会话 ID 发送给用户。 它存储在 cookie 中(默认情况下称为 PHPSESSID) 该 cookie 由浏览器随每个请求发送到服务器 服务器 (PHP) 使用包含 session_id 的 cookie 来了解哪个文件对应于该用户。

会话文件中的数据是$_SESSION的内容, 序列化 (即,表示为字符串 -- 具有诸如 serialize) ;并且在文件被加载时被取消序列化 PHP,填充$_SESSION 数组。

有时,会话 ID 不是存储在 cookie 中,而是发送到 URL 也是如此——但现在已经很少见了。

有关更多信息,您可以查看手册的Session Handling 部分,它提供了一些有用的信息 信息。

例如,有一个关于Passing the Session ID 的页面,其中 解释会话 id 如何在页面之间传递,使用 cookie,或在 URL 中——以及哪些配置选项会影响这一点。

所以,session 只是一个 cookie。那么我想我们可以使用cookie来了解用户的登录状态。

在 Firebase 中,cookie 存在问题。您只能将 cookie 名称设置为 __session。 Firebase 会忽略其他名称,因此您无法读取服务器上的 cookie。

您需要使用 Firebase Functions 来做一些后端工作并根据用户登录状态提供 html。

因此,在您的项目目录中设置 Firebase 函数。在根目录中,

$ firebase init functions

现在您必须对 Firebase 说,任何进入您的域的请求都必须调用一个函数。

为此,您必须在 firebase.json 文件中写入 rewrites。我们的函数名称将是main


  "database": 
    "rules": "database.rules.json"
  ,
  "hosting": 
    "public": "/dev",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites":[
      "source": "**",
      "function": "main"
    ]
  

现在在任何地方创建一个index.js(首选项目目录的根目录,因为这是主文件)。

index.js

import express from 'express';
import * as functions from 'firebase-functions';
import fs from 'fs';
var cookieParser = require('cookie-parser')

const app = express();
app.use(cookieParser());

app.get('**', (req, res) => 
    // Check the custom set cookie for user
    // If the user is not logged-in, redirect to Login. Otherwise, redirect to Home page
    if (req.cookies.__session == 'loggedin') 
        var index = fs.readFileSync(__dirname + '/src/Home/index.html', 'utf8');
    
    else 
        var index = fs.readFileSync(__dirname + '/src/Login/index.html', 'utf8');
    
    res.send(index);

);

export let main = functions.https.onRequest(app);

对以上部分的一些解释:

    express:用于处理请求并发送响应。

    firebase-functions:由 Google 提供,用于使用 Firebase 功能。

    fs : 我们根据用户登录状态从文件系统中读取相应的html并提供给它。

    cookie-parser:我们使用它来轻松访问我们的 cookie。

注意:您需要将其转换为 ES5。所以使用 babel 命令来转换它(我假设你在你的根目录中保存了index.js。)

$ babel index.js -d functions

我们使用名为__session 的cookie 来了解用户的登录状态。

如果__session 设置为loggedin,我们会向用户发送Home html。否则,我们发送Loginhtml

现在,真正的问题是:“我们需要在哪里设置 cookie __session?”

我们在用户浏览器中设置cookie。

我们利用onAuthStateChanged()

在您的Login page component 中调用此方法,例如:

componentDidMount() 
     this.authListener();



authListener() 
        fire.auth().onAuthStateChanged(user => 
            if (user) 
                // User is signed in.
                if (Cookies.get('__session') === undefined) 
                    Cookies.set('__session', 'loggedin',  expires: 3652 );
                    window.location.reload();
                
             else 
              // No user is signed in.
              Cookies.remove('__session');
            
        );
    

注意Cookies 对象是从 js-cookie 导入的。所以,安装它。

我们在这里做的是:

    首先在页面加载时调用onAuthStateChanged()

    如果用户已经登录,我们进入if块。

    然后我们检查__session cookie。这很重要,因为有时用户可以清除所有 cookie。当我们尝试读取服务器中的 cookie 时,我们得到undefined 并将登录页面发送给用户。从1重复处理。

    然后我们重新加载页面。当我们重新加载页面时,用户再次点击服务器。然后我们可以检查__session cookie 并将Home html 发送给用户。

【讨论】:

我有问题。如何在 html 中链接我的 javascript 文件?它说内部错误。 谢谢。您的解决方案按预期工作。如果有人提出最佳答案,我会等待几天,然后再接受答案,因为有人一直在回答我的问题。【参考方案2】:

以前,我使用 PHP,并且我使用 session 变量和 include 来包含 HTML 的部分以显示给用户。

Firebase 有多种方式保持其身份验证状态,如下所述:https://firebase.google.com/docs/auth/web/auth-state-persistence

默认为local。在网络上,这是localStorage。这对您来说可能是一个巨大的变化,因为通常身份验证状态处于某种会话或 cookie 中,它在服务器端和客户端完成。

那么,我应该如何在 ReactJS + Firebase 中进行身份验证?(是否可以不使用任何其他包,如 react-routes 等?)

但使用 Firebase,一切都在客户端完成。用户成功通过身份验证后,您将可以访问他们的身份验证令牌,如下所述:https://firebase.google.com/docs/auth/users#auth_tokens

使用此令牌,您需要在服务器端进行验证,如下所述:https://firebase.google.com/docs/auth/admin/verify-id-tokens 以保护/保护任何 API 访问。

但是为了保护 React 应用程序中的某些页面,您需要在某种中间件中验证 localStorage 中的身份验证状态。最好在您的 React 生命周期方法之一中拥有一个身份验证状态的侦听器:https://firebase.google.com/docs/reference/js/firebase.auth.Auth#onAuthStateChanged

在您的应用程序中的任何时候,身份验证状态都会变为无效,阻止他们查看页面。

【讨论】:

我的意思是我们仍在将所有组件发送给客户端。它仍然安全吗?【参考方案3】:

好吧,我向您展示了我是如何在 React 应用程序上使用 firebase Auth 的。我只是想让你知道这不是正确的方式,你会找到很多方法来做到这一点。

让我们开始吧,首先我使用 Google 身份验证,我有一个名为 firebase.service.js 的文件,我在其中处理身份验证。该文件如下所示:

import firebase from 'firebase';

import  firebaseConfig  from './private';


export const firebaseApp = firebase.initializeApp(firebaseConfig);

export const auth = firebase.auth();
export const db = firebase.database();

export function loginGoogle(/* options */) 
  const provider = new firebase.auth.GoogleAuthProvider();

  return firebase.auth().signInWithPopup(provider);

firebaseConfig 对象如下所示:

const firebaseConfig = 
    apiKey: API_KEY,
    authDomain: AUTH_DOMAIN,
    databaseURL: DB_URL,
    projectId: PROJECT_ID,
    storageBucket: STORE_BUCKET,
    messagingSenderId: MSG_SENDER,
;

您可以在 Firebase 控制台中找到它。

在安装它的应用程序的索引文件中,我将ReactDOM.render 方法与身份验证检查包装在一起,以确保用户是否已登录。索引如下:

import React from 'react';
import ReactDOM from 'react-dom';
import firebase from 'firebase';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import  auth  from './services';

auth.setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(() => 
  const stop = auth.onAuthStateChanged(() => 
    ReactDOM.render(<App />, document.getElementById('root'));
    stop();
  );
);

registerServiceWorker();

现在您可以确定,如果用户离开并返回应用程序,如果他的会话仍然有效,他将不需要再次登录。

下一步是创建PrivateRoute 组件。这是我所做的:

import * as React from 'react';
import  Redirect, Route  from 'react-router-dom';
import PropTypes from 'prop-types';

import  auth  from '../services';

function PrivateRoute( component: Component, ...rest ) 
  const toRender = propsRender => (
    auth.currentUser ?
      <Component ...propsRender /> :
      <Redirect
        to=
          pathname: '/',
          state:  from: propsRender.location ,
        
      />
  );

  return <Route ...rest render=toRender />;


PrivateRoute.propTypes = 
  component: PropTypes.func.isRequired,
  path: PropTypes.string.isRequired,
;

export default PrivateRoute;

在这里您可以看到该函数检查是否存在任何已登录的用户,否则它将用户发送回根路径。请注意,我是从./services 导入auth,而不是从firebase,这是因为此身份验证在firebase.service.js 初始化,然后我在所有应用程序中都使用它。

现在你可以使用这个PrivateRoute 作为一个普通路由,不同的是会检查用户是否登录。以此为例:

import * as React from 'react';
import  BrowserRouter, Route, Switch, Redirect  from 'react-router-dom';
import  Layout  from 'antd';
import moment from 'moment';

import 'moment/locale/pt-br';
import 'antd/dist/antd.css';

import  PrivateRoute, MainLogged, Login, Navbar, Page404  from './components';

const  Header  = Layout;

moment.locale('pt-br');

function Main() 
  return <Redirect to="/login" />;


function App() 
  return (
    <BrowserRouter>
      <Layout>
        <Header style= paddingLeft: '0', backgroundColor: '#096dd9' >
          <Navbar />
        </Header>
        <Switch>
          <Route exact path="/" component=Main />
          <Route exact path="/login" component=Login />
          <PrivateRoute path="/app" component=MainLogged />
          <Route component=Page404 />
        </Switch>
      </Layout>
    </BrowserRouter>
  );


export default App;

如您所见,PrivateRouse 与来自react-routerRoute 组件并排使用。

我以为就是这样,如果您想问其他任何问题,只需发表评论,如果您想查看我从中获取此代码的项目,您可以找到它here。

【讨论】:

首先我需要检查react-router-dom,然后我会评论你的答案 我们仍然将所有组件发送给客户端。它仍然安全吗? 是的,当你构建你的应用程序时,它会被模仿和丑化,无法理解代码。请确保不要将.map 发送到托管服务器。如果您使用 react-create-app 和 react-script 你必须从构建的应用程序中删除 .map

以上是关于使用 ReactJS 和 Firebase 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章

使用 Firebase 和 ReactJS 的基于角色的身份验证

如何使用 apollo Reactjs 客户端刷新 Firebase IdToken

如何使用 ReactJS 应用程序开发和测试 Firebase 可调用函数?

如何使用 nodejs 和 reactjs 设置 firebase 模拟器?

每次有人使用 firebase 和 Reactjs 访问页面时计算浏览量

如何使用 ReactJs 对 Cloud Firestore 数据进行分页