Viteでauth0を使ってみる ~ その③ react-router-dom の導入とルートの保護
react-router-domのインストール
- 後で使うために、2つインストールしておきます。
$ npm i react-router-dom history
フォルダ階層の変更等
ちょっと、これまでのコードを整理しました。色々、変えたので最終的にTwitterログイン後の状態で、次のように表示されるようにしました。
- 以下変更の詳細です。
- src\components\LoginButton.jsx 及び src\components\LogoutButton.jsx をフォルダ src\components\auth を新規作成し、その下に移動しました。
- src\App.jsx を次のように修正します。
import LoginButton from './components/LoginButton';
import LogoutButton from './components/LogoutButton';
↓
import LoginButton from './components/auth/LoginButton';
import LogoutButton from './components/auth/LogoutButton';
- src\App.jsx の内容を2つのページに分けることとし、src\components\pages を新規作成し、その下に Home.jsx と Profile.jsx を新規作成し、それぞれ次に設定します。
// src\components\pages\Home.jsx
import React from 'react';
import { useAuth0 } from '@auth0/auth0-react';
import LoginButton from '../auth/LoginButton';
import LogoutButton from '../auth/LogoutButton';
import logo from '../../logo.svg';
import '../../App.css';
const Home = () => {
const { user, isAuthenticated, isLoading } = useAuth0();
return (
<div className='App'>
<header className='App-header'>
<img src={logo} className='App-logo' alt='logo' />
<p>Hello Vite + React!</p>
{isLoading && <div>読み込み中...</div>}
{!isAuthenticated && <LoginButton />}
{isAuthenticated && (
<div>
<LogoutButton />
<img src={user.picture} alt={user.name} />
<h2>{user.name}</h2>
</div>
)}
</header>
</div>
);
};
export default Home;
// src\components\pages\Profile.jsx
import React, { useState, useEffect } from 'react';
import { useAuth0 } from '@auth0/auth0-react';
import LoginButton from '../auth/LoginButton';
import LogoutButton from '../auth/LogoutButton';
const Profile = () => {
const [userMetadata, setUserMetadata] = useState(null);
const { user, isAuthenticated, isLoading, getAccessTokenSilently } =
useAuth0();
useEffect(() => {
const getUserMetadata = async () => {
try {
const accessToken = await getAccessTokenSilently({
audience: import.meta.env.VITE_AUTH0_AUDIENCE,
scope: import.meta.env.VITE_AUTH0_SCOPE,
});
const userDetailsByIdUrl = `${
import.meta.env.VITE_AUTH0_AUDIENCE
}users/${user.sub}`;
const metadataResponse = await fetch(userDetailsByIdUrl, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const { description } = await metadataResponse.json();
setUserMetadata(description.replaceAll('\n', ' '));
} catch (e) {
console.log(e.message);
}
};
getUserMetadata();
}, [getAccessTokenSilently, user?.sub]);
return (
<div className='App'>
<header className='App-header'>
<h1>プロフィール</h1>
{isLoading && <div>読み込み中...</div>}
{!isAuthenticated && <LoginButton />}
{isAuthenticated && (
<div>
<LogoutButton />
<img src={user.picture} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
{userMetadata ? <div>{JSON.stringify(userMetadata)}</div> : '※なし'}
</div>
)}
</header>
</div>
);
};
export default Profile;
- ここで元のsrc\App.jsxにあったフッターのメニューは src\components\layout\FooterMenu.jsx に分離して、src\App.jsx に含めるようにしました。
// src\components\layout\FooterMenu.jsx
import React from 'react';
import { Link } from 'react-router-dom';
const FooterMenu = () => {
return (
<footer className='App-footer'>
<p>
<Link to='/' className='App-link'>
ホーム
</Link>
{' | '}
<Link to='/profile' className='App-link'>
プロフィール
</Link>
{' | '}
<a
className='App-link'
href='https://ja.reactjs.org'
target='_blank'
rel='noopener noreferrer'
>
Reactを学ぶ
</a>
{' | '}
<a
className='App-link'
href='https://ja.vitejs.dev/guide/'
target='_blank'
rel='noopener noreferrer'
>
Viteガイド
</a>
</p>
</footer>
);
};
export default FooterMenu;
- src\App.jsx と src\main.jsx は次に変更します。
// src\App.jsx
function App() {
return (
<Auth0Provider
domain={import.meta.env.VITE_AUTH0_DOMAIN}
clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
audience={import.meta.env.VITE_AUTH0_AUDIENCE}
scope={import.meta.env.VITE_AUTH0_SCOPE}
redirectUri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<Router history={history}>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/profile' component={Profile} />
</Switch>
<FooterMenu />
</Router>
</Auth0Provider>
);
}
export default App;
// src\main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
- src\App.css の .App-header をコピーした .App-footer を作り、両者の min-height を変更しました。
.App-header {
background-color: #282c34;
min-height: 90vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-footer {
background-color: #282c34;
min-height: 10vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
ルートの保護
先程の状態はTwitterログイン状態で、フッターメニューの[プロフィール]をクリックするとURLが/profileとなり、/Twitterのプロフィール文が表示され
ます。もしもログインしていない状態で [プロフィール]をクリックすると 次の画面となります。
仮にログインしていない状況ではプロフィールページには遷移できないようにしたいとします。このためには、ログインしていな場合は、 フッターメニューの[プロフィール] を表示しないようにする方法が考えられますが、localhost:3000/profile と入力すれば表示されてしまうので不完全です。
ここでは、それを防ぎ、ログインしていない場合は次の画面となる方法を説明します。
- フォルダ src\components\route を新規作成し、src\components\route\ProtectedRoute.jsx を次のようにします。
import React from 'react';
import { Route } from 'react-router-dom';
import { withAuthenticationRequired } from '@auth0/auth0-react';
const ProtectedRoute = ({ component, ...args }) => (
<Route component={withAuthenticationRequired(component)} {...args} />
);
export default ProtectedRoute;
src\App.jsx を次のように変更します。
:
import ProtectedRoute from './components/route/ProtectedRoute';
:
<Router history={history}>
<Switch>
<Route exact path='/' component={Home} />
<ProtectedRoute exact path='/profile' component={Profile} /> <--- ★RouteからProtectedRouteに変更
</Switch>
<FooterMenu />
</Router>
ディスカッション
コメント一覧
まだ、コメントがありません