4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Django REST Framework] [React] simpleJWTによるユーザー認証②

Posted at

実行環境

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認証を実装したい

前回の記事⇨
https://qiita.com/kachuno9/items/1fa592093c0fd7074aa2
でDRFによるバックエンドにおけるJWT認証の実装は終わっているのでその続きからです。
今回はReactによるフロントエンドの部分を実装しました。

個人的にはReact側の実装の方がはるかに難しく、分からないことがいっぱいあり、Qiitaの質問で沢山の方々に助けられて実装できたので、その手順を残したいと思います。

認証方法

認証方法の選定については、前回の記事に詳しく書いています。
今回はJWTをCookieに保存する方法で実装しました。

必要なライブラリのインストール

今回必要になる各ライブラリを以下の様にインストールします。(React)

$ npm install react-cookie
$ npm install react-hook-form

これらはCookieを扱うためのライブラリと、ReactHookのフォームのためのライブラリです。

React(フロントエンド)

Default.js

DefaultコンポーネントでルーティングやAPIURLの設定を行います。

Default.js
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コンポーネントでログイン処理を行います。

Login.js
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;

ログインページはこの様になっています。
スクリーンショット 2021-04-10 19.33.35.png

正しい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要素の配列で宣言する必要がある様です。

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 })} />

具体的にはこちらの通りです。
https://react-hook-form.com/migrate-v6-to-v7/

トークンをヘッダーに追加して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についての検討など課題は沢山ありますが、どんどん進めていきたいと思います。^^

参考

以下のページが非常に分かりやすく、参考にさせていただきました。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?