[Django REST Framework] [React] simpleJWTによるユーザー認証(2)
実行環境
MacOS BigSur -- 11.2.1
Python3 -- 3.8.2
Django -- 3.1.7
djangorestframework -- 3.12.2
djoser -- 2.1.0
djangorestframework-simplejwt -- 4.6.0
npm -- 6.14.4
react -- 17.0.1
react-dom -- 17.0.1
axios -- 0.21.1
react-cookie -- 4.0.3
react-hook-form -- 7.0.0
DRFとReactによるアプリケーションでJWT認証を実装したい
前回の記事⇨
今回はReactによるフロントエンドの部分を実装しました。
個人的にはReact側の実装の方がはるかに難しく、分からないことがいっぱいあり、Qiitaの質問で沢山の方々に助けられて実装できたので、その手順を残したいと思います。
認証方法
認証方法の選定については、前回の記事に詳しく書いています。
今回はJWTをCookieに保存する方法で実装しました。
必要なライブラリのインストール
今回必要になる各ライブラリを以下の様にインストールします。(React)
$ npm install react-cookie
$ npm install react-hook-form
これらはCookieを扱うためのライブラリと、ReactHookのフォームのためのライブラリです。
React(フロントエンド)
Default.js
DefaultコンポーネントでルーティングやAPIURLの設定を行います。
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Header from '../Header';
import Footer from '../Footer';
import SignUp from './SignUp';
import Login from './Login';
import Trend from './Trend';
import MyPage from './MyPage';
import PostDetail from './PostDetail';
import Top from './Top';
import Logout from './Logout';
//APIURL
export const apiURL = 'http://localhost:8000/api/v1/';
class Default extends React.Component {
render() {
return (
<div>
<Header />
<div className="main">
<Switch>
<Route exact path="/" component={Top} />
<Route exact path="/signup" component={SignUp} />
<Route exact path="/login" component={Login} />
<Route exact path="/logout" component={Logout} />
<Route exact path="/trend" component={Trend} />
<Route exact path="/mypage" component={MyPage} />
<Route exact path="/post/:id" component={PostDetail} />
<Route render={() => <p>not found!.</p>} />
</Switch>
</div>
<Footer />
</div>
);
}
}
export default Default;
Login.js
Loginコンポーネントでログイン処理を行います。
import React, { useState, useEffect, useRef } from 'react';
import { useCookies } from 'react-cookie';
import axios from 'axios';
import { useForm } from "react-hook-form";
import { useHistory } from 'react-router-dom';
import { apiURL } from './Default';
const Login = (props) => {
const history = useHistory();
const [cookies, setCookie] = useCookies();
const { register, handleSubmit, watch, errors } = useForm();
const getJwt = async (data) =>{
console.log(data)
await axios.post(`${apiURL}auth/jwt/create/`,
{
email:data.email,
password:data.password,
},
)
.then(function (response) {
console.log(response.data.access)
setCookie('accesstoken', response.data.access, { path: '/' }, { httpOnly: true });
setCookie('refreshtoken', response.data.refresh, { path: '/' }, { httpOnly: true });
history.push('/');
})
.catch(err => {
console.log("miss");
alert("EmailかPasswordが違います");
});
};
return (
<div className="top-wrapper">
<div class="login">
<h3>Login</h3>
</div>
<div class="login-block">
<form onSubmit={handleSubmit(getJwt)}>
<label for="email">Email:</label>
<input className='form-control' {...register('email')} />
<label for="password">PassWord:</label>
<input className='form-control' type="password" {...register('password', { required: true })} />
<input className='btn btn-secondary' type="submit" value="ログイン" />
</form>
</div>
</div>
);
}
export default Login;
ログインページはこの様になっています。
正しいEmail、Passwordを入力することでホーム画面に遷移し、開発者ツールでCookieを確認すると"accesstoken"と"refreshtoken"が追加されていることが確認できます。
useCookies
const [cookies, setCookie] = useCookies();
setCookie('accesstoken', response.data.access, { path: '/' }, { httpOnly: true });
setCookie('refreshtoken', response.data.refresh, { path: '/' }, { httpOnly: true });
React HookのuseCookiesを用いて取得したトークンをCookieに保存する処理を行なっています。
*ちなみに、Cookieを削除したい場合、同様にremoveCookieを使えますが、setCookieが必要無い場合にも、3要素の配列で宣言する必要がある様です。
const [cookies, setCookie, removeCookie] = useCookies();
react-hook-form
form の部分で取得したemail,passwordを用いてJWTを発行してもらう仕様になっていますが、このreact-hook-formのバージョンによって書き方が異なることで大分つまずきました。
Qiitaの質問で教えていただいたのですが、react-hook-formのV7から書き方が大幅に変わっており、以下の様な書き方はエラーを吐きます。
<input className='form-control' name="email" ref={register} />
<input className='form-control' name="password" type="password" ref={register({ required: true })} />
V7からの書き方は以下の様に変更しなければなりませんでした。
<input className='form-control' {...register('email')} />
<input className='form-control' type="password" {...register('password', { required: true })} />
具体的にはこちらの通りです。
トークンをヘッダーに追加してAPI接続
ここまでで、
① Email,Passwordを用いて、APIからJWTトークン(access,refresh)を取得
② トークンをCookieに保存
まで完了しました。あとは、③だけです。
③ ヘッダーにトークンを追加してAPI接続
これについては、axiosを用いてデータを取得する際に、axios.getメソッド内で以下の様にheadersを追加すると、DRF側でパーミッション設定がIsAuthenticatedのデータも取得できる様になります
import Cookies from 'universal-cookie';
const cookies = new Cookies();
省略
headers: {
'Content-Type': 'application/json',
'Authorization': `JWT ${cookies.get('accesstoken')}`
}
これでDRFとReactによるJWT認証の実装をある程度完了できました。まだまだセキュリティ面での対策や、UXについての検討など課題は沢山ありますが、どんどん進めていきたいと思います。^^
参考
以下のページが非常に分かりやすく、参考にさせていただきました。
Discussion