【Django REST Framework】jwt認証とは - 初心者🔰

# はじめに


ある日現場でjwt認証を使用してログイン機能を実装する可能性が浮上した。

「認証ってなんでしょうか。。。。」

エンジニアとしてのスキルが低いもので恥ずかしながら認証について知りませんでした。

なんとか実装まで持っていくことができないかを自分なりにまとめてみたいと思います。

# 目次
1. JWT認証とは
2. DRFでの実装方法

# 1. JWT認証とは
・不正なアクセスでないか？ログインユーザが合っているか？などのチェックに用いられる。
・とても簡単に以下の図のようなイメージ
![名称未設定-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737553/0ef36a8c-b76f-bcc4-5885-64aad976d777.png)


### ①ログイン情報を送るとサーバ上でログイン情報と照合する
以下のようなログインを想定した情報を送る（詳細は後述）
![スクリーンショット 2024-10-26 19.18.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737553/0cf6bcd3-fa60-fa73-cd54-3a621d227e07.png)


### ②認証されるとトークンを返却する（以下サンプル）
**・トークン**
`access`：アクセストークン
このアクセストークンをヘッダにつけることで認証を行うことができる（有効期限を持っている）

`refersh`：リフレッシュトークン
アクセストークンよりも有効期限が長く設定されており、アクセストークンの有効期限が切れた場合にこちらを使用して認証を行う。
```json
"refresh":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczMDAyMzQyMywiaWF0IjoxNzI5OTM3MDIzLCJqdGkiOiI3Y2U2NjI1Njg4Mzg0M2I1YTQ4YzY1NjVlNjZhZmJjYiIsInVzZXJfaWQiOiI1NmVlZDY3OS1mYTNlLTQ5NzUtYjIzMC1iYWQ5MTJmOTUxNjAifQ.TDj-MjAmT8NuySCCoSiAzcf0qERmlEUlKrGM9OhOyq8",
"access":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI5OTM3MzIzLCJpYXQiOjE3Mjk5MzcwMjMsImp0aSI6IjhhNTY5YzFmMTQyZTRhNmFhYjg1ZGMxMGRkNzNiYWY2IiwidXNlcl9pZCI6IjU2ZWVkNjc5LWZhM2UtNDk3NS1iMjMwLWJhZDkxMmY5NTE2MCJ9.IJjxz3QaMEbd86WmVwPI2AvBRku2_S-Wflal_rmLYkM"
```
**トークンの中身**
[https://jwt.io/](https://jwt.io/)にアクセスすることでトークンの中身を確認できる
[上記で送られたトークンの解析](https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI5OTM3MzIzLCJpYXQiOjE3Mjk5MzcwMjMsImp0aSI6IjhhNTY5YzFmMTQyZTRhNmFhYjg1ZGMxMGRkNzNiYWY2IiwidXNlcl9pZCI6IjU2ZWVkNjc5LWZhM2UtNDk3NS1iMjMwLWJhZDkxMmY5NTE2MCJ9.IJjxz3QaMEbd86WmVwPI2AvBRku2_S-Wflal_rmLYkM)
![スクリーンショット 2024-10-26 19.12.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737553/93af66e9-2b3c-62fd-997f-981118622235.png)

### ③リクエスト時にトークンをヘッダに設定する
②で送られたトークンをヘッダに設定することでリクエスト時に毎回ログインのチェックをする必要をなくす。

### ④認証後リクエストに対するレスポンスを返却する
認証情報が含まれていないとリクエストを処理できない。（不正アクセス、管理者と一般ユーザの切り分けなど）



# 2. DRFでの実装方法
### 手順１ JWT認証を行うライブラリのインストール
以下のライブラリを使用します。
```
pip install "djangorestframework-simplejwt==5.2.*"
```
これはJWT認証を提供してくれるDRF向けの認証ライブラリ
上記の①でリクエストを送ったのもこの機能によるものである。
### 手順２ URLの追加
全体設定の方のurls.pyに以下を記載する。
```python:urls.py
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
```
```swagger-ui```を使用していると以下のように表示される。
![スクリーンショット 2024-10-26 20.01.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/737553/f9aff67c-effa-0c21-cc91-52b8ad8b73bd.png)
`swagger-ui`のインストールは以下参照

https://qiita.com/shun198/items/23c6baa450ba37a5fd66


認証に必要な項目は
```python: user.py
import uuid
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.auth.models import AbstractUser, UserManager as AbstractUserManager
from django_boost.models.mixins import LogicalDeletionMixin
from django.db import models


class UserManager(AbstractUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError("The Email field must be set")
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")

        return self.create_user(email, password, **extra_fields)


class User(LogicalDeletionMixin, AbstractUser):
    username_validator = UnicodeUsernameValidator()

    class Role(models.IntegerChoices):
        MANAGEMENT = 0
        GENERAL = 1
        PART_TIME = 2

    # 不要なフィールドはNoneにすることができる
    first_name = None
    last_name = None
    date_joined = None
    groups = None
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    password = models.CharField("パスワード", max_length=100)
    username = models.CharField(
        max_length=150,
        unique=True,
        validators=[username_validator],
    )
    email = models.EmailField(max_length=254, unique=True)
    role = models.PositiveIntegerField(choices=Role.choices, default=Role.PART_TIME)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # デフォルトはusernameだが今回は社員番号を指定
    USERNAME_FIELD = "email"
    PASSWORD_FIELD = "password"
    # uniqueのemailとusernameを指定
    REQUIRED_FIELDS = ["password"]

    objects = UserManager()

    class Meta:
        db_table = "users"

    def __str__(self):
        return self.pk

```
認証に求められる項目は
以下の`email`と`password`が対象となっている。
```python
    USERNAME_FIELD = "email"
    PASSWORD_FIELD = "password"
```

これでログイン（認証機能）のベースが完成する。

# おまけ
`urls.py`に記載した`TokenObtainPairView`がこの機能をになっているため、JWTトークンにその他情報を組み込みたい場合は、`TokenObtainPairView`を継承したView、Serializerを作成することで可能となる。
```python:custom_serializer
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        data = super().validate(attrs)
        data.update({'custom_info': '任意の情報'})  # 任意の情報を追加
        return data

```

```python:custom_view
from rest_framework_simplejwt.views import TokenObtainPairView
from .serializers import CustomTokenObtainPairSerializer

class CustomTokenObtainPairView(TokenObtainPairView):
    serializer_class = CustomTokenObtainPairSerializer

```

```python: urls.py
from django.urls import path
from .views import CustomTokenObtainPairView

urlpatterns = [
    path('api/token/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
]
```

# 参考

https://techbookfest.org/product/5174523099873280