ページをリロードする形のwebサイトなどはサーバーを介してsessionなどを使用する事ができるのでログイン機能を制作するのに大きく困ることはありませんが、SPAサイトのようにクライアント側で動くアプリケーションの場合、ログイン機能の実装は難しくなります。
この記事ではReactで制作したアプリケーションに認証機能を実装する方法について解説します。
認証機能はauth0というサービスを使用して実装します。
auth0とは
auth0とはアプリケーションの認証機能を簡単に実装する事ができるサービスを提供するものです。
メールアドレスとパスワードでのログインはもちろん、GoogleアカウントTwitterアカウントなど、ソーシャルログイン、さらにはAPIの保護までもを提供する優良なサービスです。
ユーザー数が7000人まで無料で使用する事が可能となっています。
この記事で実装する内容
この記事で実装する機能は以下になります。
- 新規ユーザー登録、ログイン、ログアウト機能を制作する。
- ユーザー情報を取得する。
- ページを保護し、ログインしていないユーザーからのアクセスは拒否する。
なお今回制作するアプリケーションはReactを使用し、SPAとなっている事を対象としています。
他にもVanilla JSで実装する事や、PHPで実装する事も可能ですが、この記事では紹介しませんので、公式ドキュメントを参考にしてください。
auth0からはAuth0 React SDKというSDKが提供されており、これを使用する事で簡単にReactアプリケーションに認証機能を組み込む事ができるようになっています。
今回はこのAuth0 React SDKを使用します。
テナントの制作
auth0では認証機能を実装するアプリケーション毎にテナントと呼ばれるスペースを制作します。
このテナント1つを使用してアプリケーション1つの認証機能を実装していきます。
auth0のアカウント制作がまだの場合はアカウントを制作し、ダッシュボードへ移動します。
ダッシュボードの「Applications -> Applications」をクリックし、アプリケーション一覧を開きます。
「Create Application」ボタンをクリックする事でテナントを制作する事が可能です。
ボタンをクリックするとモーダルが出ますのでアプリケーションの名前を入力し、アプリケーションのタイプを「Single Page Web Applications」に選択し、「Create」ボタンをクリックります。
制作が完了するとこのような画面へ遷移します。
こちらの画面に遷移しなかった場合、先ほどのアプリケーション一覧から制作したテナントをクリックする事でアクセスが可能です。
今回はReactでアプリケーションを制作しますのでReactを選択します。
次にSettingsタブへ切り替え、下の方へスクロールすると「Application URIs」というセクションがあらわれます。
こちらの以下の設定を行います。
- Allowed Callback URLs(許容されるコールバックURL)
- Allowed Logout URLs(許可されたログアウトURL)
- Allowed Web Origins(許容されるウェブオリジン)
Allowed Callback URLs
ログイン成功後にリダイレクトされるURLです。
auth0のログインの流れは以下です。
アプリケーションでログインボタンをクリックする
↓
ログイン用のauth0のページへリダイレクト
↓
ログイン成功
↓
Allowed Callback URLsに入力したURLへリダイレクト
今回はローカル開発環境を使用する事を想定し、http://localhost:8000と入力します。
ポート番号はサーバーの設定よにり異なりますので環境に合わせて設定します。
Allowed Logout URLs
ログアウト後にリダイレクトされるURLです。
Allowed Callback URLsで説明した流れとまったく同じです。
こちらもローカル開発環境を想定し、http://localhost:8000と入力しておきます。
Allowed Web Origins
ログインなど、認証処理を行う際に一度auth0のページへリクエストが飛びますが、CORS(異なるURLからのリクエストを許可しない)に引っかかりますのでこの問題を回避するために設定します。
http://localhost:8000と入力しておきます。
3つの設定が完了したらページをスクロールし、「Save Changes」ボタンをクリックし設定を保存します。
次に同じページでIDなどのメモをします。
ページは閉じずにそのままにしましょう。
Auth0 React SDKのセットアップ
テナントの設定は完了しました。
次にReactコードです。
まずはAuth0 React SDKの設定をしていきます。
Auth0 React SDKをインストールしましょう。
yarn add @auth0/auth0-react npm install @auth0/auth0-react
Auth0 React SDKは、React Contextを使用しており、Auth0Contextコンポーネントを使用して、ユーザーの認証状態を管理します。
Contextで子コンポーネントへ状態を渡すため、Auth0Providerでアプリケーションをラップします。
index.js
import React from 'react'; import { Auth0Provider } from '@auth0/auth0-react'; import App from 'App'; const rootEl = document.getElementById('root'); const root = ReactDOM.createRoot(rootEl); root.render( <Auth0Provider> <App /> </Auth0Provider> );
Auth0Providerには引数としてdomain,clientId,redirectUriをセットする必要があります。
ですのでdomainとclientIdを先ほどURLをセットしたページから取得しましょう。
URLをセットした「Settings」タブの一番上に記載されています。
こちらの2つをコピーし、以下のように記載します。
import React from 'react'; import { Auth0Provider } from '@auth0/auth0-react'; import App from 'App'; const rootEl = document.getElementById('root'); const root = ReactDOM.createRoot(rootEl); const domain = "your domain"; const clientId = "your clientId"; root.render( <Auth0Provider domain={domain} clientId={clientId} redirectUri={window.location.origin} > <App /> </Auth0Provider> );
ルーターとリダイレクト処理の設定
ルーター
SPAですのでルーターの設定を行いましょう。
auth0とルーターを組み合わせる場合はルーターの子要素としてAuth0Providerを使用します。
<BrowserRouter> <Auth0Provider domain={domain} clientId={clientId} redirectUri={window.location.origin} > <App /> </Auth0Provider> </BrowserRouter>
パスやコンポーネントの割り振りはAppコンポーネント内で後程行います。
リダイレクト処理
auth0ではログイン後、元のアプリケーションにリダイレクトされ、戻されます。
リダイレクト先はAllowed Callback URLsで設定したURLとなりますが、多くのアプリケーションの場合、直前にアクセスしていたページへリダイレクトされる方がUI的に良いでしょう。
ですのでその設定をuseHistoryを使用して制作します。
auth0-provider-with-history.jsを制作し、以下のコードを記載します。
import React from 'react'; import { useHistory } from 'react-router-dom'; import { Auth0Provider } from '@auth0/auth0-react'; const Auth0ProviderWithHistory = ({ children }) => { const domain = "your domain"; const clientId = "your clientId"; const history = useHistory(); const onRedirectCallback = (appState) => { history.push(appState?.returnTo || window.location.pathname); }; return ( <Auth0Provider domain={domain} clientId={clientId} redirectUri={window.location.origin} onRedirectCallback={onRedirectCallback} > {children} </Auth0Provider> ); }; export default Auth0ProviderWithHistory;
そして、index.jsの方を以下のように修正します。
import React from 'react'; import { Auth0Provider } from '@auth0/auth0-react'; import Auth0ProviderWithHistory from './auth/auth0-provider-with-history'; import App from 'App'; const rootEl = document.getElementById('root'); const root = ReactDOM.createRoot(rootEl); root.render( <BrowserRouter> <Auth0ProviderWithHistory> <App /> </Auth0ProviderWithHistory> </BrowserRouter> );
解説
- Auth0Providerをindex.jsからauth0-provider-with-historyへ移動しました。
- 移動にともないdomainやclientIdも移動しています。
- Auth0Providerには新しくonRedirectCallbackをついかしています。
- onRedirectCallbackは認証が成功し、リダイレクトで戻った時に実行されます。
- リダイレクトされるとonRedirectCallback処理が走り、アクセス履歴から前に訪れていたページを取得し、useHistoryを使用してページを移動させます。
これでSDKのセットアップは完了です。
ユーザー認証機能の実装
サインアップ(新規登録)、ログイン、ログアウト機能を制作します。
ログイン機能
components/ディレクトリを制作し、login-button.jsを制作しましょう。
components/login-button.js
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const LoginButton = () => { const { loginWithRedirect } = useAuth0(); return ( <button onClick={() => loginWithRedirect()} > Log In </button> ); }; export default LoginButton;
useAuth0を使用し、loginWithRedirect関数を取り出します。
ボタンのクリックイベントでloginWithRedirectを実行する事でログイン画面へリダイレクトさせる事が可能となります。
サインアップ機能
components/signup-button.jsを制作し以下のコードを記載します。
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const SigniupButton = () => { const { loginWithRedirect } = useAuth0(); return ( <button onClick={() => loginWithRedirect({ screen_hint: 'signup', }) } > Sign In </button> ); }; export default SignupButton;
サインアップ機能はログインで使用したloginWithRedirectの引数にオプションを渡す事で可能です。
{ screen_hint: 'signup' }
ログアウト機能
components/logout-button.jsを制作して以下のコードを記載します。
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const LogoutButton = () => { const { logout } = useAuth0(); return ( <button onClick={() => logout({ returnTo: window.location.origin, }) } > Log Out </button> ); }; export default LogoutButton;
ログインと同じようにuseAuth0からlogout関数を取り出し、クリックイベントで実行します。
ログアウトが実行されるとauth0のエンドポイント/v2/logoutへリダイレクトし、セッションが消去され、Allowed Logout URLsで設定したURLへリダイレクトされます。
現在はlocalhosへ設定していますが、本番環境へデプロイした際にlocal環境へリダイレクトされては困ります。
Allowed Logout URLsを変更するという方法もありますが、returnToオプションを使用する事で解決する事が可能です。
ログアウト後のreturnToはリダイレクト先を指定する事が可能です。
window.location.originを指定する事で現在のページへ戻る事が可能です。
ボタンを設置する
アプリケーションのメインとなるApp.jsを制作し、制作した認証系ボタンを設置していきます。
import React from 'react'; import { useAuth0 } from "@auth0/auth0-react"; import LoginButton from "./components/LoginButton"; import SignupButton from "./components/SignupButton"; import LogoutButton from "./components/LogoutButton"; const App = () => { const { isAuthenticated } = useAuth0(); return ( <> <header> <nav className='p-nav'> navigation </nav> <div> {isAuthenticated ? <LogoutButton /> : <><SignupButton /><LoginButton /></>} </div> </header> <main className='l-main'> main content </main> <footer>footer</footer> </> ); };
ログイン/ログアウトボタンはログイン状態によって表示を切り替えています。
ログインしている状態ではログアウトボタンを表示し、ログアウトしている状態ではログインとサインアップボタンを表示します。
ログインの状態はuseAuth0のisAuthenticatedの値を見る事でわかります。
ログインしている時はisAuthenticatedが true。ログインしていない時はfalseとなっています。
ルーティングで他のページを制作する
次のセクションでユーザー情報を取得する実装をします。
取得した情報を表示するページを制作しましょう。
pagesディレクトリを制作し、pages/Profile.jsを制作します。
一緒にpages/Home.jsも制作します。
pages/Profile.js
import React from 'react'; const Profile = () => { return ( <div> <h1>Profile</h1> </div> ) } export default Profile;
pages/Home.js
import React from 'react'; const Home = () => { return ( <div> <h1>Home</h1> </div> ) } export default Home;
制作した2つのページをApp.jsでルーティング設定します。
import React from 'react'; import { Routes, Route, NavLink } from "react-router-dom"; import { useAuth0 } from "@auth0/auth0-react"; import Home from "./pages/Home"; import Profile from "./pages/Profile"; import LoginButton from "./components/LoginButton"; import SignupButton from "./components/SignupButton"; import LogoutButton from "./components/LogoutButton"; const App = () => { const { isAuthenticated } = useAuth0(); return ( <> <header> <nav className='p-nav'> <ul className='p-nav__ul'> <li className='p-nav__li'><NavLink to="/">Home</NavLink></li> <li className='p-nav__li'><NavLink to="/profile">Profile</NavLink></li> </ul> </nav> <div> {isAuthenticated ? <LogoutButton /> : <><SignupButton /><LoginButton /></>} </div> </header> <main className='l-main'> <Routes> <Route path='/' element={<Home/>} /> <Route path='/profile' element={<Profile/>}/> </Routes> </main> <footer>footer</footer> </> ); }
ユーザー情報を取得する
auth0ではGoogleアカウントなどのソーシャルログインを行う事がで来ます。
ソーシャルログイン後、そのアカウントから暗号化されたユーザー情報を取得する事ができ、Auth0 React SDK内でデコードされ、useAuth0のuserに格納されます。
pages/Profile.jsを以下のように修正します。
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const Profile = () => { const { user } = useAuth0(); return ( <div> <img src={user.picture} alt={user.name} /> <p>name: {user.name}</p> <p>email: {user.email}</p> <pre> {JSON.stringify(user, null, 2)} </pre> </div> ); }; export default Profile;
ユーザー情報は個人情報が含まれます。
ですので、このページはログインしていない状態では見る事ができないようにするべきです。
ログイン機能を実装しているなら他にも対象のユーザー以外には見せたくない画面はあるでしょう。
次のセクションでページを他のユーザーから保護する実装を行います。
ページを保護する
Auth0 React SDKにはログインしていないユーザーが対象のページを開こうとするとログインページへリダイレクトしてくれる機能を提供するwithAuthenticationRequiredというHookが存在します。
withAuthenticationRequiredの使い方は以下です。
pages/Profile.js
import React from 'react'; import { useAuth0, withAuthenticationRequired } from '@auth0/auth0-react'; const Profile = () => { const { user } = useAuth0(); return ( 略 ... ); }; export default withAuthenticationRequired(Profile, { onRedirecting: () => <p>Loading ...</p>, });
withAuthenticationRequiredは保護したいページのエクスポート部分に使用します。
withAuthenticationRequiredの第一引数にページのコンポーネントをセットし、第二引数にオプションをセットする事が可能です。
onRedirectingを使用する事でログインしていない状態の時に変りに表示するコンテンツをセットする事ができます。
withAuthenticationRequiredをより使いやすくする
今回はルーターを使用していますが、現所のwithAuthenticationRequiredの使い方はルーターからはdのページが保護されているかがわかりずらいです。
ですので、ルーターから保護されているページが一目でわかるようにwithAuthenticationRequiredを修正しましょう。
auth/protected-route.jsを制作し、以下のコードを記載します。
import React from 'react'; import { withAuthenticationRequired } from '@auth0/auth0-react'; import Loading from "../components/Loading"; const ProtectedRoute = ({ component, prop }) => { const Component = withAuthenticationRequired(component, { onRedirecting: () => <Loading />, }); return <Component prop={prop} />; }; export default ProtectedRoute;
onRedirectingで代わりに表示するコンテンツはLoadingコンポーネントへとおきかえました。
このProtectedRouteをApp.jsで読み込み、Routeのelementからわたす事でルーティングを行ているページで一目に保護ページと理解できるようにします。
App.jsのルーティング設定部分を以下のように修正します。
<Routes> <Route path='/' element={<Home/>} /> <Route path='/profile' element={<ProtectedRoute component={Profile}/>} /> <Route path='/dashboard' element={<ProtectedRoute component={Dashboard} prop="propを渡す事も可能です" />} /> </Routes>
保護する必要のあるProfileページを表示するRouteのelementにProtectedRouteをセットします。
ProfileコンポーネントはProtectedRouteのcomponentから渡し、propからは何かpropを渡す事も可能です。
これで各ページ内でwithAuthenticationRequiredの設定をする必用はなくなったので、pages/Profile.jsのwithAuthenticationRequiredを消去します。
import React from 'react'; import { useAuth0, withAuthenticationRequired } from '@auth0/auth0-react'; const Profile = () => { const { user } = useAuth0(); return ( 略 ... ); }; export default Profile;
参考ページ集
Add Login to your React App[公式]
Auth0 APIs[公式]
Auth0 Videos[公式チュートリアル]
公式ブログ(技術的な事、実装方法が載っています)