Reactアプリケーションにログイン認証を追加する方法
著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
多くのWebアプリケーションは、パブリックページとプライベートページが混在しています。 パブリックページは誰でも利用できますが、プライベートページはユーザーログインが必要です。 authentication を使用して、どのユーザーがどのページにアクセスできるかを管理できます。 React アプリケーションは、ユーザーがログインする前にプライベートページにアクセスしようとする状況を処理する必要があり、ユーザーが正常に認証されたらログイン情報を保存する必要があります。
このチュートリアルでは、トークンベースの認証システムを使用してReactアプリケーションを作成します。 ユーザートークンを返すモックAPIを作成し、トークンを取得するログインページを作成し、ユーザーを再ルーティングせずに認証を確認します。 ユーザーが認証されていない場合は、ユーザーがログインして、専用のログインページに移動せずに続行できるようにする機会を提供します。 アプリケーションを構築するときは、トークンを保存するためのさまざまな方法を検討し、セキュリティを学習し、各アプローチのトレードオフを経験します。 このチュートリアルでは、localStorageおよびsessionStorageにトークンを保存することに焦点を当てます。
このチュートリアルを終了するまでに、Reactアプリケーションに認証を追加し、ログインおよびトークンストレージ戦略を完全なユーザーワークフローに統合できるようになります。
前提条件
-
Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.22.0およびnpmバージョン6.14.6でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはの
PPAを使用したインストール ]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。 -
Create React App でセットアップされたReact開発環境で、不要なボイラープレートが削除されています。 これを設定するには、ステップ1 —Reactクラスコンポーネントの状態を管理する方法のチュートリアルの空のプロジェクトを作成します。 このチュートリアルでは、
auth-tutorial
プロジェクト名として。 -
Reactを使用してAPIからデータをフェッチします。 ReactのuseEffectフックを使用してWebAPIを呼び出す方法でAPIの操作について学ぶことができます。
-
また、JavaScript、HTML、およびCSSの基本的な知識も必要です。これらは、 HTMLシリーズでWebサイトを構築する方法、 CSSでHTMLをスタイル設定する方法、およびJavaScriptでコーディングする方法。
ステップ1—ログインページを作成する
このステップでは、アプリケーションのログインページを作成します。 まず、 React Router をインストールし、完全なアプリケーションを表すコンポーネントを作成します。 次に、ユーザーが新しいページにリダイレクトされることなくアプリケーションにログインできるように、任意のルートでログインページをレンダリングします。
この手順を完了すると、ユーザーがアプリケーションにログインしていないときにログインページを表示する基本的なアプリケーションができあがります。
まず、reactrouterをインストールします npm
. 2つの異なるバージョンがあります。ReactNativeで使用するWebバージョンとネイティブバージョンです。 Webバージョンをインストールします。
- 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つのコンポーネントを作成します。 Dashboard
と Preferences
プライベートページとして機能します。 これらは、ユーザーがアプリケーションに正常にログインするまで表示されないコンポーネントを表します。
まず、ディレクトリを作成します。
- mkdir src/components/Dashboard
- mkdir src/components/Preferences
次に開きます Dashboard.js
テキストエディタで。 このチュートリアルでは、nanoを使用します。
- nano src/components/Dashboard/Dashboard.js
の中に Dashboard.js
、を追加します <h2>
の内容でタグ付け Dashboard
:
import React from 'react';
export default function Dashboard() {
return(
<h2>Dashboard</h2>
);
}
ファイルを保存して閉じます。
同じ手順を繰り返します Preferences
. コンポーネントを開きます。
- nano src/components/Preferences/Preferences.js
コンテンツを追加します。
import React from 'react';
export default function Preferences() {
return(
<h2>Preferences</h2>
);
}
ファイルを保存して閉じます。
いくつかのコンポーネントができたので、コンポーネントをインポートして、内部にルートを作成する必要があります。 App.js
. Reactアプリケーションでのルーティングの完全な紹介については、チュートリアルReactRouterを使用してReactアプリでルーティングを処理する方法を確認してください。
開始するには、 App.js
:
- nano src/components/App/App.js
次にインポート Dashboard
と Preferences
次の強調表示されたコードを追加します。
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
:
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>
とともに className
の wrapper
と <h1>
アプリケーションのテンプレートとして機能するタグ。 インポートしていることを確認してください App.css
スタイルを適用できるようにします。
次に、のルートを作成します Dashboard
と Preferences
コンポーネント。 追加 BrowserRouter
、次に追加します Switch
子としてのコンポーネント。 の内部 Switch
、を追加します Route
とともに path
コンポーネントごとに:
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
:
- nano src/components/App/App.css
内容を次のクラスに置き換えます .wrapper
と padding
の 20px
:
.wrapper {
padding: 20px;
}
ファイルを保存して閉じます。 これを行うと、ブラウザがリロードされ、基本的なコンポーネントが見つかります。
各ルートを確認してください。 http:// localhost:3000 / dashboard にアクセスすると、ダッシュボードページが表示されます。
ルートは期待どおりに機能していますが、少し問題があります。 ルート /dashboard
保護されたページである必要があり、認証されていないユーザーが表示できないようにする必要があります。 プライベートページを処理するにはさまざまな方法があります。 たとえば、ログインページの新しいルートを作成し、 React Routerを使用して、ユーザーがログインしていない場合にリダイレクトすることができます。 これは優れたアプローチですが、ユーザーはルートを失い、最初に表示したいページに戻る必要があります。
邪魔にならないオプションは、ルートに関係なくログインページを生成することです。 このアプローチでは、ユーザートークンが保存されていない場合にログインページをレンダリングし、ユーザーがログインすると、最初にアクセスしたのと同じルートになります。 つまり、ユーザーが訪問した場合 /dashboard
、彼らはまだ上にあります /dashboard
ログイン後のルート。
開始するには、の新しいディレクトリを作成します Login
成分:
- mkdir src/components/Login
次に、開く Login.js
テキストエディタの場合:
- nano src/components/Login/Login.js
送信して基本的なフォームを作成する <button>
と <input>
ユーザー名とパスワード。 パスワードの入力タイプは、必ず次のように設定してください。 password
:
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>
とともに className
の login-wrapper
. 最後に、インポート Login.css
:
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
:
- nano src/components/Login/Login.css
を追加して、コンポーネントをページの中央に配置します display
の flex
、次に設定 flex-direction
に column
要素を垂直に配置して追加する align-items
に center
コンポーネントをブラウザの中央に配置するには:
.login-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
Flexboxの使用の詳細については、CSSFlexboxチートシートを参照してください。
ファイルを保存して閉じます。
最後に、内部でレンダリングする必要があります App.js
ユーザートークンがない場合。 開ける App.js
:
- nano src/components/App/App.js
ステップ3では、トークンを保存するためのオプションについて説明します。 今のところ、 useStateHookを使用してトークンをメモリに保存できます。
輸入 useState
から react
、次に電話 useState
戻り値をに設定します token
と setToken
:
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
成分:
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を有効にしないでください。 これはセキュリティの脆弱性につながる可能性があります。
- 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
ディレクトリを最終ビルドの一部にしたくないので。
- nano server.js
輸入 express
、次にを呼び出して新しいアプリを初期化します express()
結果をという変数に保存します app
:
const express = require('express');
const app = express();
作成後 app
、 追加 cors
ミドルウェアとして。 まず、インポート cors
、次に、を呼び出してアプリケーションに追加します use
上の方法 app
:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
次に、特定のルートを聞きます app.use
. 最初の引数はアプリケーションがリッスンするパスであり、2番目の引数はアプリケーションがパスを提供するときに実行されるコールバック関数です。 コールバックは req
引数。リクエストデータと res
結果を処理する引数。
のハンドラーを追加します /login
道。 電話 res.send
トークンを含むJavaScriptオブジェクトを使用:
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
:
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'));
ファイルを保存して閉じます。 新しいターミナルウィンドウまたはタブで、サーバーを起動します。
- node server.js
サーバーが起動していることを示す応答を受け取ります。
OutputAPI is running on http://localhost:8080/login
http:// localhost:8080 / login にアクセスすると、JSONオブジェクトが見つかります。
ブラウザでトークンを取得すると、 GET
リクエストしますが、ログインフォームを送信すると、 POST
リクエスト。 問題ない。 でルートを設定するとき app.use
、Expressはすべてのリクエストを同じように処理します。 本番アプリケーションでは、より具体的に、各ルートに対して特定のリクエストメソッドのみを許可する必要があります。
APIサーバーを実行しているので、ログインページからリクエストを行う必要があります。 開ける Login.js
:
- nano src/components/Login/Login.js
前のステップで、という新しいpropを渡しました setToken
に Login
成分。 を追加します PropType
新しいプロップとdestructureから、プロップオブジェクトを引き出して setToken
小道具。
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
}
次に、ローカル状態を作成してキャプチャします Username
と Password
. 手動でデータを設定する必要がないので、 <inputs>
制御されていないコンポーネント。 制御されていないコンポーネントの詳細については、Reactでフォームを作成する方法を参照してください。
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
オプション:
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
とともに username
と password
. 電話 setToken
成功した結果で。 電話 handleSubmit
を使用して onSubmit
上のイベントハンドラ <form>
:
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—でユーザートークンを保存する sessionStorage
と localStorage
このステップでは、ユーザートークンを保存します。 さまざまなトークンストレージオプションを実装し、各アプローチのセキュリティへの影響を学習します。 最後に、ユーザーが新しいタブを開いたり、セッションを閉じたりしたときに、さまざまなアプローチによってユーザーエクスペリエンスがどのように変化するかを学習します。
このステップの終わりまでに、アプリケーションの目標に基づいてストレージアプローチを選択できるようになります。
トークンを保存するためのいくつかのオプションがあります。 すべてのオプションにはコストとメリットがあります。 簡単に言うと、オプションは、JavaScriptメモリへの保存、 sessionStorage への保存、 localStorage への保存、cookieへの保存です。 主なトレードオフはセキュリティです。 現在のアプリケーションのメモリの外部に保存されている情報は、クロスサイトスクリプティング(XSS)攻撃に対して脆弱です。 危険なのは、悪意のあるユーザーがアプリケーションにコードをロードできる場合、悪意のあるユーザーがアクセスできることです。 localStorage
, sessionStorage
、およびアプリケーションからもアクセス可能なCookie。 非メモリストレージ方式の利点は、ユーザーエクスペリエンスを向上させるために、ユーザーがログインする必要がある回数を減らすことができることです。
このチュートリアルでは、 sessionStorage
と localStorage
、これらはCookieを使用するよりも新しいためです。
セッションストレージ
メモリの外部に保存することの利点をテストするには、メモリ内のストレージを次のように変換します sessionStorage
. 開ける App.js
:
- nano src/components/App/App.js
への呼び出しを削除します useState
と呼ばれる2つの新しい関数を作成します setToken
と getToken
. 次に電話 getToken
結果をと呼ばれる変数に割り当てます token
:
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
および変換されたオブジェクト。
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
:
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
:
- nano src/components/App/useToken.js
これは小さなフックになり、で直接定義した場合は問題ありません App.js
. ただし、カスタムフックを別のファイルに移動すると、コンポーネントの外部でフックがどのように機能するかがわかります。 このフックを複数のコンポーネントで再利用し始める場合は、別のディレクトリに移動することもできます。
中身 useToken.js
、 輸入 useState
から react
. インポートする必要がないことに注意してください React
ファイルにJSXが含まれないためです。 と呼ばれる関数を作成してエクスポートします useToken
. この関数内で、 useState
を作成するためのフック token
状態と setToken
関数:
import { useState } from 'react';
export default function useToken() {
const [token, setToken] = useState();
}
次に、 getToken
機能する useHook
内部に配置したので、矢印関数に変換します useToken
. 関数を標準の名前付き関数のままにしておくこともできますが、最上位の関数が標準で、内部関数が矢印関数の場合は読みやすくなります。 ただし、各チームは異なります。 1つのスタイルを選択し、それに固執します。
場所 getToken
状態宣言の前に、初期化 useState
と getToken
. これにより、トークンがフェッチされ、初期状態として設定されます。
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
:
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);
};
}
最後に、を含むオブジェクトを返します token
と saveToken
に設定 setToken
プロパティ名。 これにより、コンポーネントに同じインターフェイスが提供されます。 値を配列として返すこともできますが、オブジェクトを使用すると、これを別のコンポーネントで再利用する場合に、必要な値のみを非構造化することができます。
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
:
- nano src/components/App/App.js
を削除します getToken
と setToken
機能。 次にインポート useToken
を破壊する関数を呼び出します setToken
と token
値。 のインポートを削除することもできます useState
フックを使用しなくなったため:
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
:
- nano src/components/App/useToken.js
次に、のすべての参照を変更します sessionStorage
に localStorage
. 呼び出すメソッドは同じになります。
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
、ただし、そうした後は、新しいタブを開いたときにログインしたままになります。
このステップでは、でトークンを保存しました sessionStorage
と localStorage
. また、コンポーネントの再レンダリングをトリガーし、コンポーネントロジックを別の関数に移動するためのカスタムフックを作成しました。 また、その方法についても学びました sessionStorage
と localStorage
ログインせずに新しいセッションを開始するユーザーの能力に影響を与えます。
結論
認証は、多くのアプリケーションの重要な要件です。 セキュリティ上の懸念とユーザーエクスペリエンスの組み合わせは恐ろしいものになる可能性がありますが、データの検証とコンポーネントの適切なタイミングでのレンダリングに焦点を当てると、軽量のプロセスになる可能性があります。
各ストレージソリューションには、明確な長所と短所があります。 アプリケーションが進化するにつれて、選択が変わる可能性があります。 コンポーネントロジックを抽象的なカスタムフックに移動することで、既存のコンポーネントを中断することなくリファクタリングできるようになります。
Reactチュートリアルをもっと読みたい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。