[譯] 用 React 和 Node.js 實現受保護的路由和許可權驗證

ElizurHz發表於2018-12-21

上週末我想挖掘一些沒有 Redux-Saga 這種花裡胡哨的東西的純粹的 React

所以我建立了一個小專案,在 Strapi — 一個包括了可擴充套件的管理後臺皮膚和一些內建功能(授權,上傳,許可權控制...)的 Node.js 框架的配合下,僅使用 Create React App 建立一個小模板來實現授權流程。

React Nodejs

在本教程中,我們會使用 Strapi 的 API 提供的 JSON Web Tokens 快速地實現基本的授權流程,並且會一步步教大家在 Strapi 中使用第三方登陸授權提供器(Facebook, GitHub, Google...)來授權你的使用者登入(這可能會更有趣)。

Strapi authentication

注: 本文的原始碼可以在 GitHub 上找到。

建立專案

在開始之前,你需要建立一個 Strapi API:

$ npm install strapi@alpha -g
$ strapi new my-app
$ cd my-app && strapi start
複製程式碼

和你的前端應用:

$ npm install create-react-app -g
$ create-react-app good-old-react-authentication-flow
複製程式碼

你需要 先註冊第一個使用者,然後就可以開始了!

前端應用構架

我是 React Boilerplate 框架的忠實粉絲,所以我建立了一個類似的應用來組織我的程式碼:

/src
└─── containers // 與路由相關的 React 元件
|    └─── App // 應用的入口
|    └─── AuthPage // 負責所有授權頁面的元件
|    └─── ConnectPage // 負責使用第三方提供器進行授權
|    └─── HomePage // 只能在使用者登陸後訪問到
|    └─── NotFoundPage // 404 元件
|    └─── PrivateRoute // 高階元件
|
└─── components // 展示元件
|
└─── utils
     └─── auth
     └─── request // 使用 fetch 的網路請求輔助庫
複製程式碼

設定路由和 PrivateRoute

為了實現身份驗證的檢視,我們需要先建立一個 HoC高階元件 來檢查是否使用者可以訪問一個特定的 URL。為此,我們只需要遵循 官方文件,修改 fakeAuth 示例,並使用我們的 auth.js 輔助檔案:

import React from 'react';  
import { Redirect, Route } from 'react-router-dom';

// Utils
import auth from '../../utils/auth';

const PrivateRoute = ({ component: Component, ...rest }) => (  
  <Route {...rest} render={props => (
    auth.getToken() !== null ? (
      <Component {...props} />
    ) : (
      <Redirect to={{
        pathname: 'auth/login',
        state: { from: props.location }
        }}
      />
    ):
  )} />
);

export default PrivateRoute;  
複製程式碼

然後我們來建立路由吧:

import React, { Component } from 'react';  
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';

// Components
import AuthPage from '../../containers/AuthPage';  
import ConnectPage from '../../containers/ConnectPage';  
import HomePage from '../../containers/HomePage';  
import NotFoundPage from '../../containers/NotFoundPage';

// 這個元件是用於防止未登入使用者訪問特定路由的高階元件
import PrivateRoute from '../../containers/PrivateRoute';

// Design
import './styles.css';

class App extends Component {  
  render() {
    return (
      <Router>
        <div className="App">
          <Switch>
            {/* A user can't go to the HomePage if is not authenticated */}
            <PrivateRoute path="/" component={HomePage} exact />
            <Route path="/auth/:authType/:id?" component={AuthPage} />
            <Route exact path="/connect/:provider" component={ConnectPage} />
            <Route path="" component={NotFoundPage} />
          </Switch>
        </div>
      </Router>
    );
  }
}

export default App;
複製程式碼

建立授權檢視

現在所有需要用於建立檢視的路由都已經實現了。 我們宣告路由的方式允許我們建立一個能夠根據 路徑 建立正確的表單的元件。

首先,讓我們建立 forms.json 來處理在每個 auth 檢視中建立表單的操作:

  • forgot-password
  • login
  • register
  • reset-password

JSON 結構如下所示(你可以發現在 Input 元件中 customBootstrapClass 這個熟悉是必需的):

{
  "views": {    
    "login": [
      {
        "customBootstrapClass": "col-md-12",
        "label": "Username",
        "name": "identifier",
        "type": "text",
        "placeholder": "johndoe@gmail.com"
      },
      {
        "customBootstrapClass": "col-md-12",
        "label": "Password",
        "name": "password",
        "type": "password"
      },
      {
        "customBootstrapClass": "col-md-6",
        "label": "Remember me",
        "name": "rememberMe",
        "type": "checkbox"
      }
    ]
  },
  "data": {
    "login": {
      "identifier": "",
      "password": "",
      "rememberMe": false
    }
  }
}
複製程式碼

當路由變化時設定 state

如果要在使用者從路由 auth/login 切換到路由 auth/register 時設定表單,我們需要使用以下生命週期:

componentDidMount() {  
  // 使用一個函式生成表單以防
  // 表單在其他生命週期裡重複
  this.generateForm(this.props);
}
複製程式碼
componentWillReceiveProps(nextProps) {  
  // 因為我們對所有的 auth 檢視使用同樣的容器
  // 所以我們需要在路徑改變的時候更新 UI
  if (nextProps.location.match.params.authType !== this.props.location.match.params.authType) {
    this.generateForm(nextProps);
  }
}
複製程式碼

generateForm 方法負責從上面的 forms.json 檔案中獲取資料。

建立檢視

要建立表單,我們只需要對映 forms.json 中的資料。

handleChange = ({ target }) => this.setState({ value: { ...this.state.value, [target.name]: target.value } });

render() {  
  const inputs = get(forms, ['views', this.props.match.params.authType, []);

  return (
    <div>
      <form onSubmit={this.handleSubmit}>
        {inputs.map((input, key) => (
          <Input
            autoFocus={key === 0}
            key={input.name}
            name={input.name}
            onChange={this.handleChange}
            type={input.type}
            value={get(this.state.value, [input.name], '')}
          />
        ))}
        <Button type="submit" />
      </form>
    </div>
  );
}
複製程式碼

Strapi login view

那麼此時,所有授權使用者需要的檢視都應該已經建立好了!我們只需要進行 API 呼叫即可訪問該應用。

將資料釋出到 API

為了進行 API 呼叫,我寫了一個 request 的輔助檔案(你可以在這裡訪問 demo app),我們只需要在我們的 handleSubmit 函式中使用它:

handleSubmit = (e) => {  
  e.preventDefault();
  const body = this.state.value;
  const requestURL = 'http://localhost:1337/auth/local';

  request(requestURL, { method: 'POST', body: this.state.value})
    .then((response) => {
      auth.setToken(response.jwt, body.rememberMe);
      auth.setUserInfo(response.user, body.rememberMe);
      this.redirectUser();
    }).catch((err) => {
      console.log(err);
    });
}

redirectUser = () => {  
  this.props.history.push('/');
}
複製程式碼

這裡沒有什麼花裡胡哨的操作,當我們獲得了 API 的響應後,我們只要將所需的資訊存到 localStorage 或者 sessionStorage 中,然後我們可以將使用者重定向至 HomePage。

我們剛實現了最困難的部分,因為使用像 Facebook 這樣的第三方授權提供器非常容易!

使用授權提供器

無論你選擇 Facebook、GitHub 還是 Google,在 Strapi 使用第三方授權提供器來授權你的使用者登陸是非常簡單的 ?。在這個例子中,我將為大家展示怎樣使用 Facebook 的第三方授權提供器。

因為 Strapi()沒有提供 Javascript SDK 來對接 Strapi 的 API 和 Facebook 的 API。

具體流程如下:

  • 使用者“點選使用 Facebook 登入”
  • 將使用者重定向至另一個頁面,在那裡他可以進行授權
  • 授權之後,Facebook 會將使用者重定向到你的應用裡,並帶在 URL 中附帶一個 code
  • 把這個 code 傳送給 Strapi

此時,我們只需要在 componentDidMount 生命週期中發起 API 的請求,然後根據 ConnectPage 容器中的響應內容將使用者重定向至相應頁面:

componentDidMount() {  
  const { match: {params: { provider }}, location: { search } } = this.props;
  const requestURL = `http://localhost:1337/auth/${provider}/callback${search}`;

 request(requestURL, { method: 'GET' })
   .then((response) => {
      auth.setToken(response.jwt, true);
      auth.setUserInfo(response.user, true);
      this.redirectUser('/');
   }).catch(err => {
      console.log(err.response.payload)
      this.redirectUser('/auth/login');
   });
}

redirectUser = (path) => {  
  this.props.history.push(path);
}
複製程式碼

在 AuthPage 中顯示授權提供器

為此,我們需要一個如下所示的 SocialLink 元件:

/**
*
* SocialLink
*
*/

import React from 'react';  
import PropTypes from 'prop-types';

import Button from '../../components/Button'

function SocialLink({ provider }) {  
  return (
    <a href={`http://localhost:1337/connect/${provider}`} className="link">
      <Button type="button" social={provider}>
        <i className={`fab fa-${provider}`} />
        {provider}
      </Button>
    </a>
  );
}

SocialLink.propTypes = {  
  provider: PropTypes.string.isRequired,
};

export default SocialLink;
複製程式碼

然後我們需要把它加入到 AuthPage 中:

render() {  
  const providers = ['facebook', 'github', 'google', 'twitter']; // 如果要把一個提供器移除,只要把它從這個陣列中刪除即可...

  return (
     <div>
       {providers.map(provider => <SocialLink provider={provider} key={provider} />)}
       {/* Some other code */}
     </div>
  );
}
複製程式碼

Login page

這些就是我們在前端應用中需要做的,現在只需要配置 Strapi 來啟用第三方授權提供器 ?

設定 Facebook 授權提供器來進行使用者註冊

Facebook developers 並且建立一個名叫 test 的應用。

  • 在 product 區域新增 Facebook login
  • 選擇 Web
  • 將 Site URL 設為 http://localhost:3000

Facebook setup

  • 從 Dashboard 頁面中拷貝 App Id 和 App Secret 到你的應用中

Facebook setup

  • Facebook login > Advanced settings 中,新增:http://localhost:1337/connect/facebook/callbackValid OAuth redirect URIs 欄位。

Facebook setup

配置 Strapi

現在你已經在 Facebook 上建立了一個可以用於配置你專案中 Facebook 提供器的應用。

Users & Permissions 區域的 Providers 標籤頁,按照如下所示填寫表單:

Admin FB setup

不要忘記儲存修改。

結論

希望這個小教程可以幫助你使用 ReactStrapi 進行使用者授權登陸。

我認為這個工作量不大,而且很簡單!你可以在 這裡 找到這個週末我使用 Create React App 建立的模板。

這裡 也有另一個使用 React Boilerplate 的完整的例子,它也是已經完整實現了整個授權的流程。第二個例子使用了 React 和 Redux-Saga,它也是我們用於構建基於 Strapi 的管理後臺的模板。

大家可以分享並在評論中留言!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章