【簡単】django_restframeworkのjwt認証を最低限実装していく

# 目的
スロットリングの実装は以前に行い、APIを叩く回数に制限をかけることでブルートフォース攻撃などに対策を行なったが、**それだけでAPIに関するセキュリティが万全と言えるわけもない。**
**なぜなら誰でもAPIを叩ける状態になっているから。**そこでこのAPIを認証されたユーザーだけが制限された回数叩けるように認証周りを実装していきたい。

# 実施環境
### ハードウェア環境
| 項目 | 情報 |
|:-:|:-:|
| OS  |macOS Catalina(10.15.7)   |
|ハードウェア   |  MacBook Air (11-inch, Early 2015) |
|プロセッサ   | 1.6 GHz デュアルコアIntel Core i5|
|メモリ   |  4 GB 1600 MHz DDR3 |
|グラフィックス   |  intel HD Graphics 6000 1536 MB |
### ソフトウェア環境
| 項目 | 情報 |
|:-:|:-:|
| homebrew  | 3.3.8 |
| mysql | Ver 8.0.27 for macos10.15 on x86_64 |
| python  | 3.8.12 |
| anaconda  | 4.10.1 |
| django  | 21.2.4 |
| pip  | 3.1.2 |

# 概要

usernameとpasswordでaccess_tokenを発行する時にはrefresh_tokenも同時に発行され、フロントのlocalStorageやCookieに保存される。
access_tokenの有効期限が切れている場合には、フロントのlocalStorageやCookieから有効期限がaccess_tokenよりも長いrefresh_tokenを利用してもう一度access_tokenを発行することができる

# JWTトークンを取得する
###「Django REST framework JWT」インストール
```terminal
$ pip install djangorestframework-simplejwt==4.6.*
```
### setting.pyに設定を追加する
ACCESS_TOKEN_LIFETIMEはAuthトークンの有効期限を自分で好きな時間に変更する。


```settings.py
REST_FRAMEWORK = {
    # jwtトークン認証
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),  
}
SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT'),
    # JWT有効期限
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
}
```
### 設定ディレクトリのurls.pyにエンドポイントを実装する
simplejwtライブラリに入っているTokenObtainPairViewとTokenRefreshViewへのルーティングを実装

```urls.py
from django.contrib import admin
from django.db import router
from django.urls import path, include
from rest_framework_simplejwt import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('tinder.urls')),
    # jwt-tokenを取得
    path('api-auth/jwt/', views.TokenObtainPairView.as_view()),
    # jwt-tokenを再取得
    path('api-auth/jwt/refresh', views.TokenRefreshView.as_view()),
]
```
### postmanやcurlコマンドでAPIを叩く
画像のように、上記で実装したurls.pyのAPIエンドポイントを叩くと
**※ トークンを取得するが、POSTメソッドでなければ許可されないため注意
※ usernameとpasswordをBodyに含める必要がある。**

![スクリーンショット 2021-12-27 午後6.52.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487834/be436317-e34e-f1ac-3a0d-9a31c6da4d57.png)

# 実際にJWT認証をViews.pyに適用する
下記のように指定のViewsの認証を追加する

```views.py
from rest_framework.permissions import IsAuthenticated

class MemberViewSet(viewsets.ModelViewSet):
    queryset = Members.objects.all()
    serializer_class = MemberSerializer
    # 今回の追加部分
    permission_classes = (IsAuthenticated,)
```
viewごとにpermission_classesでどのような制限をかけるかを指定できる。
今回はMemberViewSetを利用できるのはログイン認証ができているユーザーのみ。
**つまり、headerのAuthorizationに先ほど取得したaccess_tokenを入れてAPIエンドポイントを叩いた場合のみAPIを利用できる。**
※ ```permission_classes = (IsAuthenticated,)```この部分で```,```がないとエラーになるため要注意

### access_tokenをHeaderに含めていない状態
認証状態が含まれていないと指摘される。
無事にこのAPIを叩くにはaccess_tokenが必要になったようだ

```json
{
    "detail": "認証情報が含まれていません。"
}
```
![スクリーンショット 2021-12-27 午後7.30.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487834/a3af2e82-20ed-e13a-59a0-2264359efaa9.png)

### access_tokenをHeaderに含めた状態
headersのAuthorizationに先ほど取得したaccess_tokenを入れてもう一度APIを叩いてみる。

![スクリーンショット 2021-12-27 午後7.30.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487834/82950e4c-fbcf-4fe6-9cc2-a67ee8575cf0.png)


無事JSONデータを取得できた。


# 参考文献

[「Django REST framework JWT」でAPI認証を行う](https://create-it-myself.com/know-how/perform-api-authentication-with-django-rest-framework-jwt/)

[Django REST framework に JWTログイン機能を実装する](https://qiita.com/Syoitu/items/bc32b5e1c2fa891c2f96)