著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

多くのWebアプリケーションは、パブリックページとプライベートページが混在しています。 パブリックページは誰でも利用できますが、プライベートページはユーザーログインが必要です。 authentication を使用して、どのユーザーがどのページにアクセスできるかを管理できます。 React アプリケーションは、ユーザーがログインする前にプライベートページにアクセスしようとする状況を処理する必要があり、ユーザーが正常に認証されたらログイン情報を保存する必要があります。

このチュートリアルでは、トークンベースの認証システムを使用してReactアプリケーションを作成します。 ユーザートークンを返すモックAPIを作成し、トークンを取得するログインページを作成し、ユーザーを再ルーティングせずに認証を確認します。 ユーザーが認証されていない場合は、ユーザーがログインして、専用のログインページに移動せずに続行できるようにする機会を提供します。 アプリケーションを構築するときは、トークンを保存するためのさまざまな方法を検討し、セキュリティを学習し、各アプローチのトレードオフを経験します。 このチュートリアルでは、localStorageおよびsessionStorageにトークンを保存することに焦点を当てます。

このチュートリアルを終了するまでに、Reactアプリケーションに認証を追加し、ログインおよびトークンストレージ戦略を完全なユーザーワークフローに統合できるようになります。

前提条件

ステップ1—ログインページを作成する

このステップでは、アプリケーションのログインページを作成します。 まず、 React Router をインストールし、完全なアプリケーションを表すコンポーネントを作成します。 次に、ユーザーが新しいページにリダイレクトされることなくアプリケーションにログインできるように、任意のルートでログインページをレンダリングします。

この手順を完了すると、ユーザーがアプリケーションにログインしていないときにログインページを表示する基本的なアプリケーションができあがります。

まず、reactrouterをインストールします npm. 2つの異なるバージョンがあります。ReactNativeで使用するWebバージョンとネイティブバージョンです。 Webバージョンをインストールします。

  1. npm install react-router-dom

パッケージがインストールされ、インストールが完了するとメッセージが表示されます。 メッセージは若干異なる場合があります。

Output
... + [email protected] added 11 packages from 6 contributors, removed 10 packages and audited 1945 packages in 12.794s ...

次に、2つのコンポーネントを作成します。 DashboardPreferences プライベートページとして機能します。 これらは、ユーザーがアプリケーションに正常にログインするまで表示されないコンポーネントを表します。

まず、ディレクトリを作成します。

  1. mkdir src/components/Dashboard
  2. mkdir src/components/Preferences

次に開きます Dashboard.js テキストエディタで。 このチュートリアルでは、nanoを使用します。

  1. nano src/components/Dashboard/Dashboard.js

の中に Dashboard.js、を追加します <h2> の内容でタグ付け Dashboard:

auth-tutorial / src / components / Dashboard / Dashboard.js
import React from 'react';

export default function Dashboard() {
  return(
    <h2>Dashboard</h2>
  );
}

ファイルを保存して閉じます。

同じ手順を繰り返します Preferences. コンポーネントを開きます。

  1. nano src/components/Preferences/Preferences.js

コンテンツを追加します。

auth-tutorial / src / components / Preferences / Preferences.js
import React from 'react';

export default function Preferences() {
  return(
    <h2>Preferences</h2>
  );
}

ファイルを保存して閉じます。

いくつかのコンポーネントができたので、コンポーネントをインポートして、内部にルートを作成する必要があります。 App.js. Reactアプリケーションでのルーティングの完全な紹介については、チュートリアルReactRouterを使用してReactアプリでルーティングを処理する方法を確認してください。

開始するには、 App.js:

  1. nano src/components/App/App.js

次にインポート DashboardPreferences 次の強調表示されたコードを追加します。

auth-tutorial / src / components / App / App.js
import React from 'react';
import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Preferences from '../Preferences/Preferences';

function App() {
  return (
    <></>
  );
}

export default App;

次に、インポート BrowserRouter, Switch、 と Route から react-router-dom:

auth-tutorial / src / components / App / App.js
import React from 'react';
import './App.css';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Dashboard from '../Dashboard/Dashboard';
import Preferences from '../Preferences/Preferences';

function App() {
  return (
    <></>
  );
}

export default App;

周囲を追加 <div> とともに classNamewrapper<h1> アプリケーションのテンプレートとして機能するタグ。 インポートしていることを確認してください App.css スタイルを適用できるようにします。

次に、のルートを作成します DashboardPreferences コンポーネント。 追加 BrowserRouter、次に追加します Switch 子としてのコンポーネント。 の内部 Switch、を追加します Route とともに path コンポーネントごとに:

チュートリアル/src/components/App/App.js
import React from 'react';
import './App.css';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Dashboard from '../Dashboard/Dashboard';
import Preferences from '../Preferences/Preferences';

function App() {
  return (
    <div className="wrapper">
      <h1>Application</h1>
      <BrowserRouter>
        <Switch>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/preferences">
            <Preferences />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

ファイルを保存して閉じます。

最後のステップは、メインにパディングを追加することです <div> そのため、コンポーネントはブラウザの端に直接配置されていません。 これを行うには、CSSを変更します。

開ける App.css:

  1. nano src/components/App/App.css

内容を次のクラスに置き換えます .wrapperpadding20px:

auth-tutorial / src / components / App / App.css
.wrapper {
    padding: 20px;
}

ファイルを保存して閉じます。 これを行うと、ブラウザがリロードされ、基本的なコンポーネントが見つかります。

各ルートを確認してください。 http:// localhost:3000 / dashboard にアクセスすると、ダッシュボードページが表示されます。

ルートは期待どおりに機能していますが、少し問題があります。 ルート /dashboard 保護されたページである必要があり、認証されていないユーザーが表示できないようにする必要があります。 プライベートページを処理するにはさまざまな方法があります。 たとえば、ログインページの新しいルートを作成し、 React Routerを使用して、ユーザーがログインしていない場合にリダイレクトすることができます。 これは優れたアプローチですが、ユーザーはルートを失い、最初に表示したいページに戻る必要があります。

邪魔にならないオプションは、ルートに関係なくログインページを生成することです。 このアプローチでは、ユーザートークンが保存されていない場合にログインページをレンダリングし、ユーザーがログインすると、最初にアクセスしたのと同じルートになります。 つまり、ユーザーが訪問した場合 /dashboard、彼らはまだ上にあります /dashboard ログイン後のルート。

開始するには、の新しいディレクトリを作成します Login 成分:

  1. mkdir src/components/Login

次に、開く Login.js テキストエディタの場合:

  1. nano src/components/Login/Login.js

送信して基本的なフォームを作成する <button><input> ユーザー名とパスワード。 パスワードの入力タイプは、必ず次のように設定してください。 password:

auth-tutorial / src / components / Login / Login.js
import React from 'react';

export default function Login() {
  return(
    <form>
      <label>
        <p>Username</p>
        <input type="text" />
      </label>
      <label>
        <p>Password</p>
        <input type="password" />
      </label>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  )
}

Reactのフォームの詳細については、チュートリアルReactでフォームを作成する方法を確認してください。

次に、 <h1> ユーザーにログインを求めるタグ。 ラップ <form> そしてその <h1><div> とともに classNamelogin-wrapper. 最後に、インポート Login.css:

auth-tutorial / src / components / Login / Login.js
import React from 'react';
import './Login.css';

export default function Login() {
  return(
    <div className="login-wrapper">
      <h1>Please Log In</h1>
      <form>
        <label>
          <p>Username</p>
          <input type="text" />
        </label>
        <label>
          <p>Password</p>
          <input type="password" />
        </label>
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>
  )
}

ファイルを保存して閉じます。

これで基本的なものができました Login コンポーネントには、いくつかのスタイリングを追加する必要があります。 開ける Login.css:

  1. nano src/components/Login/Login.css

を追加して、コンポーネントをページの中央に配置します displayflex、次に設定 flex-directioncolumn 要素を垂直に配置して追加する align-itemscenter コンポーネントをブラウザの中央に配置するには:

auth-tutorial / src / components / Login / Login.css
.login-wrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
}

Flexboxの使用の詳細については、CSSFlexboxチートシートを参照してください。

ファイルを保存して閉じます。

最後に、内部でレンダリングする必要があります App.js ユーザートークンがない場合。 開ける App.js:

  1. nano src/components/App/App.js

ステップ3では、トークンを保存するためのオプションについて説明します。 今のところ、 useStateHookを使用してトークンをメモリに保存できます。

輸入 useState から react、次に電話 useState 戻り値をに設定します tokensetToken:

auth-tutorial / src / components / App / App.js
import React, { useState } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Preferences from '../Preferences/Preferences';

function App() {
  const [token, setToken] = useState();
  return (
    <div className="wrapper">
      <h1>Application</h1>
      <BrowserRouter>
        <Switch>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/preferences">
            <Preferences />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

をインポートします Login 成分。 条件文を追加して表示します Login の場合 token 偽物です。

合格 setToken 機能する Login 成分:

auth-tutorial / src / components / App / App.js
import React, { useState } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';

import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';

function App() {
  const [token, setToken] = useState();

  if(!token) {
    return <Login setToken={setToken} />
  }

  return (
    <div className="wrapper">
      <h1>Application</h1>
      <BrowserRouter>
        <Switch>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/preferences">
            <Preferences />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

今のところ、トークンはありません。 次のステップでは、APIを呼び出し、戻り値を使用してトークンを設定します。

ファイルを保存して閉じます。 これを行うと、ブラウザがリロードされ、ログインページが表示されます。 http:// localhost:3000 / dashboard にアクセスすると、トークンがまだ設定されていないため、ログインページが表示されます。

このステップでは、トークンを設定するまで表示されるプライベートコンポーネントとログインコンポーネントを使用してアプリケーションを作成しました。 また、ページを表示するようにルートを構成し、チェックを追加して Login ユーザーがまだアプリケーションにログインしていない場合は、すべてのルートのコンポーネント。

次のステップでは、ユーザートークンを返すローカルAPIを作成します。 からAPIを呼び出します Login コンポーネントを作成し、成功したらトークンをメモリに保存します。

ステップ2—トークンAPIを作成する

このステップでは、ユーザートークンをフェッチするためのローカルAPIを作成します。 Node.js を使用してモックAPIを構築し、ユーザートークンを返します。 次に、ログインページからそのAPIを呼び出し、トークンを正常に取得した後にコンポーネントをレンダリングします。 この手順を完了すると、ログインページと保護されたページが機能するアプリケーションが作成され、ログイン後にのみアクセスできるようになります。

トークンを返すバックエンドとして機能するサーバーが必要になります。 Node.jsとExpressWebフレームワークを使用して、サーバーをすばやく作成できます。 Expressサーバーの作成の詳細については、チュートリアルNode.jsの基本的なExpressサーバーを参照してください。

開始するには、 express. サーバーは最終ビルドの要件ではないため、必ずdevDependencyとしてインストールしてください。

corsもインストールする必要があります。 このライブラリは、すべてのルートでクロスオリジンリソースシェアリングを有効にします。

警告:本番アプリケーションのすべてのルートでCORSを有効にしないでください。 これはセキュリティの脆弱性につながる可能性があります。

  1. npm install --save-dev express cors

インストールが完了すると、成功メッセージが表示されます。

Output
... + [email protected] + [email protected] removed 10 packages, updated 2 packages and audited 2059 packages in 12.597s ...

次に、という新しいファイルを開きます server.js アプリケーションのルートにあります。 このファイルをに追加しないでください /src ディレクトリを最終ビルドの一部にしたくないので。

  1. nano server.js

輸入 express、次にを呼び出して新しいアプリを初期化します express() 結果をという変数に保存します app:

auth-tutorial / server.js
const express = require('express');
const app = express();

作成後 app、 追加 cors ミドルウェアとして。 まず、インポート cors、次に、を呼び出してアプリケーションに追加します use 上の方法 app:

auth-tutorial / server.js
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());

次に、特定のルートを聞きます app.use. 最初の引数はアプリケーションがリッスンするパスであり、2番目の引数はアプリケーションがパスを提供するときに実行されるコールバック関数です。 コールバックは req 引数。リクエストデータと res 結果を処理する引数。

のハンドラーを追加します /login 道。 電話 res.send トークンを含むJavaScriptオブジェクトを使用:

auth-tutorial / server.js
const express = require('express');
const cors = require('cors')
const app = express();

app.use(cors());

app.use('/login', (req, res) => {
  res.send({
    token: 'test123'
  });
});

最後に、ポートでサーバーを実行します 8080 を使用して app.listen:

auth-tutorial / server.js
const express = require('express');
const cors = require('cors')
const app = express();

app.use(cors());

app.use('/login', (req, res) => {
  res.send({
    token: 'test123'
  });
});

app.listen(8080, () => console.log('API is running on http://localhost:8080/login'));

ファイルを保存して閉じます。 新しいターミナルウィンドウまたはタブで、サーバーを起動します。

  1. node server.js

サーバーが起動していることを示す応答を受け取ります。

Output
API is running on http://localhost:8080/login

http:// localhost:8080 / login にアクセスすると、JSONオブジェクトが見つかります。

ブラウザでトークンを取得すると、 GET リクエストしますが、ログインフォームを送信すると、 POST リクエスト。 問題ない。 でルートを設定するとき app.use、Expressはすべてのリクエストを同じように処理します。 本番アプリケーションでは、より具体的に、各ルートに対して特定のリクエストメソッドのみを許可する必要があります。

APIサーバーを実行しているので、ログインページからリクエストを行う必要があります。 開ける Login.js:

  1. nano src/components/Login/Login.js

前のステップで、という新しいpropを渡しました setTokenLogin 成分。 を追加します PropType 新しいプロップとdestructureから、プロップオブジェクトを引き出して setToken 小道具。

auth-tutorial / src / components / Login / Login.js

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

import './Login.css';

export default function Login({ setToken }) {
  return(
    <div className="login-wrapper">
      <h1>Please Log In</h1>
      <form>
        <label>
          <p>Username</p>
          <input type="text" />
        </label>
        <label>
          <p>Password</p>
          <input type="password" />
        </label>
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>
  )
}

Login.propTypes = {
  setToken: PropTypes.func.isRequired
}

次に、ローカル状態を作成してキャプチャします UsernamePassword. 手動でデータを設定する必要がないので、 <inputs> 制御されていないコンポーネント。 制御されていないコンポーネントの詳細については、Reactでフォームを作成する方法を参照してください。

auth-tutorial / src / components / Login / Login.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';

import './Login.css';

export default function Login({ setToken }) {
  const [username, setUserName] = useState();
  const [password, setPassword] = useState();
  return(
    <div className="login-wrapper">
      <h1>Please Log In</h1>
      <form>
        <label>
          <p>Username</p>
          <input type="text" onChange={e => setUserName(e.target.value)}/>
        </label>
        <label>
          <p>Password</p>
          <input type="password" onChange={e => setPassword(e.target.value)}/>
        </label>
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>
  )
}

Login.propTypes = {
  setToken: PropTypes.func.isRequired
};

次に、関数を作成して POST サーバーへのリクエスト。 大規模なアプリケーションでは、これらを別のディレクトリに追加します。 この例では、サービスをコンポーネントに直接追加します。 ReactコンポーネントでのAPIの呼び出しの詳細については、チュートリアルReactでuseEffectフックを使用してWebAPIを呼び出す方法を確認してください。

非同期関数を作成します loginUser. 関数はかかります credentials 引数として、それは呼び出します fetch を使用する方法 POST オプション:

auth-tutorial / src / components / Login / Login.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './Login.css';

async function loginUser(credentials) {
 return fetch('http://localhost:8080/login', {
   method: 'POST',
   headers: {
     'Content-Type': 'application/json'
   },
   body: JSON.stringify(credentials)
 })
   .then(data => data.json())
}

export default function Login({ setToken }) {
...

最後に、というフォーム送信ハンドラーを作成します handleSubmit それは loginUser とともに usernamepassword. 電話 setToken 成功した結果で。 電話 handleSubmit を使用して onSubmit 上のイベントハンドラ <form>:

auth-tutorial / src / components / Login / Login.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './Login.css';

async function loginUser(credentials) {
 return fetch('http://localhost:8080/login', {
   method: 'POST',
   headers: {
     'Content-Type': 'application/json'
   },
   body: JSON.stringify(credentials)
 })
   .then(data => data.json())
}

export default function Login({ setToken }) {
  const [username, setUserName] = useState();
  const [password, setPassword] = useState();

  const handleSubmit = async e => {
    e.preventDefault();
    const token = await loginUser({
      username,
      password
    });
    setToken(token);
  }

  return(
    <div className="login-wrapper">
      <h1>Please Log In</h1>
      <form onSubmit={handleSubmit}>
        <label>
          <p>Username</p>
          <input type="text" onChange={e => setUserName(e.target.value)} />
        </label>
        <label>
          <p>Password</p>
          <input type="password" onChange={e => setPassword(e.target.value)} />
        </label>
        <div>
          <button type="submit">Submit</button>
        </div>
      </form>
    </div>
  )
}

Login.propTypes = {
  setToken: PropTypes.func.isRequired
};

注:完全なアプリケーションでは、Promiseが解決する前にコンポーネントがアンマウントされる状況を処理する必要があります。 詳細については、チュートリアルReactのuseEffectフックを使用してWebAPIを呼び出す方法を確認してください。

ファイルを保存して閉じます。 ローカルAPIがまだ実行されていることを確認してから、ブラウザを開いて http:// localhost:3000 /dashboardにアクセスします。

ダッシュボードの代わりにログインページが表示されます。 フォームに記入して送信すると、Webトークンを受け取り、ダッシュボードのページにリダイレクトされます。

これで、動作するローカルAPIと、ユーザー名とパスワードを使用してトークンを要求するアプリケーションができました。 しかし、まだ問題があります。 トークンは現在、ローカル状態を使用して保存されています。つまり、JavaScriptメモリに保存されています。 新しいウィンドウやタブを開いたり、ページを更新したりすると、トークンが失われ、ユーザーは再度ログインする必要があります。 これについては、次のステップで対処します。

このステップでは、アプリケーションのローカルAPIとログインページを作成しました。 トークンを送信するノードサーバーを作成する方法と、サーバーを呼び出してログインコンポーネントからトークンを保存する方法を学習しました。 次のステップでは、ページの更新またはタブ間でセッションが持続するように、ユーザートークンを保存する方法を学習します。

ステップ3—でユーザートークンを保存する sessionStoragelocalStorage

このステップでは、ユーザートークンを保存します。 さまざまなトークンストレージオプションを実装し、各アプローチのセキュリティへの影響を学習します。 最後に、ユーザーが新しいタブを開いたり、セッションを閉じたりしたときに、さまざまなアプローチによってユーザーエクスペリエンスがどのように変化するかを学習します。

このステップの終わりまでに、アプリケーションの目標に基づいてストレージアプローチを選択できるようになります。

トークンを保存するためのいくつかのオプションがあります。 すべてのオプションにはコストとメリットがあります。 簡単に言うと、オプションは、JavaScriptメモリへの保存、 sessionStorage への保存、 localStorage への保存、cookieへの保存です。 主なトレードオフはセキュリティです。 現在のアプリケーションのメモリの外部に保存されている情報は、クロスサイトスクリプティング(XSS)攻撃に対して脆弱です。 危険なのは、悪意のあるユーザーがアプリケーションにコードをロードできる場合、悪意のあるユーザーがアクセスできることです。 localStorage, sessionStorage、およびアプリケーションからもアクセス可能なCookie。 非メモリストレージ方式の利点は、ユーザーエクスペリエンスを向上させるために、ユーザーがログインする必要がある回数を減らすことができることです。

このチュートリアルでは、 sessionStoragelocalStorage、これらはCookieを使用するよりも新しいためです。

セッションストレージ

メモリの外部に保存することの利点をテストするには、メモリ内のストレージを次のように変換します sessionStorage. 開ける App.js:

  1. nano src/components/App/App.js

への呼び出しを削除します useState と呼ばれる2つの新しい関数を作成します setTokengetToken. 次に電話 getToken 結果をと呼ばれる変数に割り当てます token:

auth-tutorial / src / components / App / App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';

import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';

function setToken(userToken) {
}

function getToken() {
}

function App() {
  const token = getToken();

  if(!token) {
    return <Login setToken={setToken} />
  }

  return (
    <div className="wrapper">
     ...
    </div>
  );
}

export default App;

同じ関数名と変数名を使用しているため、コードを変更する必要はありません。 Login コンポーネントまたは残りの部分 App 成分。

の中に setToken、 を助けて userToken への引数 sessionStorage を使用して setItem 方法。 このメソッドは、最初の引数としてキーを取り、2番目の引数として文字列を取ります。 つまり、変換する必要があります userToken を使用してオブジェクトから文字列に JSON.stringify 関数。 電話 setItem のキーで token および変換されたオブジェクト。

auth-tutorial / src / components / App / App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';

import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';

function setToken(userToken) {
  sessionStorage.setItem('token', JSON.stringify(userToken));
}

function getToken() {
}

function App() {
  const token = getToken();

  if(!token) {
    return <Login setToken={setToken} />
  }

  return (
    <div className="wrapper">
      ...
    </div>
  );
}

export default App;

ファイルを保存します。 これを行うと、ブラウザがリロードされます。 ユーザー名とパスワードを入力して送信すると、ブラウザは引き続きログインページを表示しますが、ブラウザのコンソールツールを見ると、トークンがに保存されていることがわかります。 sessionStorage. この画像はFirefoxのものですが、Chromeやその他の最新のブラウザでも同じ結果が得られます。

次に、正しいページをレンダリングするためにトークンを取得する必要があります。 内部 getToken 関数、呼び出し sessionStorage.getItem. この方法は key 引数として、文字列値を返します。 を使用して文字列をオブジェクトに変換します JSON.parse、次にの値を返します token:

auth-tutorial / src / components / App / App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';

function setToken(userToken) {
  sessionStorage.setItem('token', JSON.stringify(userToken));
}

function getToken() {
  const tokenString = sessionStorage.getItem('token');
  const userToken = JSON.parse(tokenString);
  return userToken?.token
}

function App() {
  const token = getToken();

  if(!token) {
    return <Login setToken={setToken} />
  }

  return (
    <div className="wrapper">
      ...
    </div>
  );
}

export default App;

オプションの連鎖演算子を使用する必要があります—?.-にアクセスするとき token 最初にアプリケーションにアクセスしたときのプロパティの値 sessionStorage.getItem('token') になります undefined. プロパティにアクセスしようとすると、エラーが発生します。

ファイルを保存して閉じます。 この場合、すでにトークンが保存されているため、ブラウザが更新されると、プライベートページに移動します。

開発者ツールのストレージタブでトークンを削除するか、次のように入力して、トークンをクリアします。 sessionStorage.clear() 開発者コンソールで。

今少し問題があります。 ログインすると、ブラウザはトークンを保存しますが、ログインページは引き続き表示されます。

問題は、トークンの取得が成功したことをコードがReactに警告しないことです。 データが変更されたときに再レンダリングをトリガーする状態を設定する必要があります。 Reactのほとんどの問題と同様に、それを解決する方法は複数あります。 最もエレガントで再利用可能なものの1つは、カスタムフックを作成することです。

カスタムトークンフックの作成

カスタムフックは、カスタムロジックをラップする関数です。 カスタムフックは通常、1つ以上の組み込みのReactフックをカスタム実装とともにラップします。 カスタムフックの主な利点は、コンポーネントから実装ロジックを削除して、複数のコンポーネントで再利用できることです。

慣例により、カスタムフックはキーワードで始まります use*.

で新しいファイルを開きます App と呼ばれるディレクトリ useToken.js:

  1. nano src/components/App/useToken.js

これは小さなフックになり、で直接定義した場合は問題ありません App.js. ただし、カスタムフックを別のファイルに移動すると、コンポーネントの外部でフックがどのように機能するかがわかります。 このフックを複数のコンポーネントで再利用し始める場合は、別のディレクトリに移動することもできます。

中身 useToken.js、 輸入 useState から react. インポートする必要がないことに注意してください React ファイルにJSXが含まれないためです。 と呼ばれる関数を作成してエクスポートします useToken. この関数内で、 useState を作成するためのフック token 状態と setToken 関数:

auth-tutorial / src / components / App / useToken.js
import { useState } from 'react';

export default function useToken() {
  const [token, setToken] = useState();

}

次に、 getToken 機能する useHook 内部に配置したので、矢印関数に変換します useToken. 関数を標準の名前付き関数のままにしておくこともできますが、最上位の関数が標準で、内部関数が矢印関数の場合は読みやすくなります。 ただし、各チームは異なります。 1つのスタイルを選択し、それに固執します。

場所 getToken 状態宣言の前に、初期化 useStategetToken. これにより、トークンがフェッチされ、初期状態として設定されます。

auth-tutorial / src / components / App / useToken.js
import { useState } from 'react';

export default function useToken() {
  const getToken = () => {
    const tokenString = sessionStorage.getItem('token');
    const userToken = JSON.parse(tokenString);
    return userToken?.token
  };
  const [token, setToken] = useState(getToken());

}

次に、 setToken からの機能 App.js. 矢印関数に変換し、新しい関数に名前を付けます saveToken. トークンをに保存することに加えて sessionStorage、を呼び出してトークンを状態に保存します setToken:

auth-tutorial / src / components / App / useToken.js

import { useState } from 'react';

export default function useToken() {
  const getToken = () => {
    const tokenString = sessionStorage.getItem('token');
    const userToken = JSON.parse(tokenString);
    return userToken?.token
  };

  const [token, setToken] = useState(getToken());

  const saveToken = userToken => {
    sessionStorage.setItem('token', JSON.stringify(userToken));
    setToken(userToken.token);
  };
}

最後に、を含むオブジェクトを返します tokensaveToken に設定 setToken プロパティ名。 これにより、コンポーネントに同じインターフェイスが提供されます。 値を配列として返すこともできますが、オブジェクトを使用すると、これを別のコンポーネントで再利用する場合に、必要な値のみを非構造化することができます。

auth-tutorial / src / components / App / useToken.js
import { useState } from 'react';

export default function useToken() {
  const getToken = () => {
    const tokenString = sessionStorage.getItem('token');
    const userToken = JSON.parse(tokenString);
    return userToken?.token
  };

  const [token, setToken] = useState(getToken());

  const saveToken = userToken => {
    sessionStorage.setItem('token', JSON.stringify(userToken));
    setToken(userToken.token);
  };

  return {
    setToken: saveToken,
    token
  }
}

ファイルを保存して閉じます。

次に、開く App.js:

  1. nano src/components/App/App.js

を削除します getTokensetToken 機能。 次にインポート useToken を破壊する関数を呼び出します setTokentoken 値。 のインポートを削除することもできます useState フックを使用しなくなったため:

auth-tutorial / src / components / App / App.js
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import './App.css';
import Dashboard from '../Dashboard/Dashboard';
import Login from '../Login/Login';
import Preferences from '../Preferences/Preferences';
import useToken from './useToken';

function App() {

  const { token, setToken } = useToken();

  if(!token) {
    return <Login setToken={setToken} />
  }

  return (
    <div className="wrapper">
      <h1>Application</h1>
      <BrowserRouter>
        <Switch>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/preferences">
            <Preferences />
          </Route>
        </Switch>
      </BrowserRouter>
    </div>
  );
}

export default App;

ファイルを保存して閉じます。 これを行うと、ブラウザが更新され、ログインするとすぐにそのページに移動します。 あなたが呼んでいるのでこれは起こっています useState カスタムフックで、コンポーネントの再レンダリングをトリガーします。

これで、トークンを保存するためのカスタムフックができました sessionStorage. これでページを更新でき、ユーザーはログインしたままになります。 ただし、別のタブでアプリケーションを開こうとすると、ユーザーはログアウトされます。 sessionStorage 特定のウィンドウセッションにのみ属します。 新しいタブではデータを利用できず、アクティブなタブを閉じるとデータが失われます。 タブ間でトークンを保存する場合は、に変換する必要があります localStorage.

使用する localStorage Windows間でデータを保存するには

ようではない sessionStorage, localStorage セッション終了後もデータを保存します。 これは、ユーザーが新しいログインなしで複数のウィンドウとタブを開くことができるため、より便利ですが、セキュリティ上の問題がいくつかあります。 ユーザーがコンピューターを共有している場合、ブラウザーを閉じてもアプリケーションにログインしたままになります。 明示的にログアウトするのはユーザーの責任です。 次のユーザーは、ログインしなくてもアプリケーションにすぐにアクセスできます。 これはリスクですが、アプリケーションによっては便利なだけの価値がある場合があります。

に変換するには localStorage、 開いた useToken.js:

  1. nano src/components/App/useToken.js

次に、のすべての参照を変更します sessionStoragelocalStorage. 呼び出すメソッドは同じになります。

auth-tutorial / src / components / App / useToken.js
import { useState } from 'react';

export default function useToken() {
  const getToken = () => {
    const tokenString = localStorage.getItem('token');
    const userToken = JSON.parse(tokenString);
    return userToken?.token
  };

  const [token, setToken] = useState(getToken());

  const saveToken = userToken => {
    localStorage.setItem('token', JSON.stringify(userToken));
    setToken(userToken.token);
  };

  return {
    setToken: saveToken,
    token
  }
}

ファイルを保存します。 これを行うと、ブラウザが更新されます。 まだトークンがないため、再度ログインする必要があります localStorage、ただし、そうした後は、新しいタブを開いたときにログインしたままになります。

このステップでは、でトークンを保存しました sessionStoragelocalStorage. また、コンポーネントの再レンダリングをトリガーし、コンポーネントロジックを別の関数に移動するためのカスタムフックを作成しました。 また、その方法についても学びました sessionStoragelocalStorage ログインせずに新しいセッションを開始するユーザーの能力に影響を与えます。

結論

認証は、多くのアプリケーションの重要な要件です。 セキュリティ上の懸念とユーザーエクスペリエンスの組み合わせは恐ろしいものになる可能性がありますが、データの検証とコンポーネントの適切なタイミングでのレンダリングに焦点を当てると、軽量のプロセスになる可能性があります。

各ストレージソリューションには、明確な長所と短所があります。 アプリケーションが進化するにつれて、選択が変わる可能性があります。 コンポーネントロジックを抽象的なカスタムフックに移動することで、既存のコンポーネントを中断することなくリファクタリングできるようになります。

Reactチュートリアルをもっと読みたい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。