【Django】GraphQLのJWT認証を構築

# 概要
DjangoにGraphQLサーバーを構築し、JWT認証を実装を紹介します。
Djangoのユーザで認証できるようにし、リフレッシュトークンはDBに保存しすぐ無効化できるように設定します。
また、一度使用したリフレッシュトークンは無効化し、再度利用できないように設定します。
別サーバーからアクセスすることを想定し、CORSの設定も行います。

## バージョン
- python 3.9.0
- django 3.1.3
- django-graphql-jwt 0.3.1
- django-cors-headers 3.5.0

## ライブラリのインストール
Djangoのプロジェクトが既に作成済みとして話を進めます。（名前はconfig）

```
$ pip install django-graphql-jwt==0.3.1 django-cors-headers==3.5.0
```

## Settingファイルの修正

##### 1.`AuthenticationMiddleware`を`MIDDLEWARE`に追加します。
```python
MIDDLEWARE = [
    # 追加
    'django.contrib.auth.middleware.AuthenticationMiddleware',
]
```
##### 2.`JSONWebTokenMiddleware`を`GRAPHENE`に追加します。
```python
GRAPHENE = {
    # configというプロジェクトを作成して、schemaというファイルを作成しています。
    'SCHEMA': 'config.schema.schema',
    'MIDDLEWARE': [
        # 追加
        'graphql_jwt.middleware.JSONWebTokenMiddleware',
    ],
}
```

##### 3.`JSONWebTokenBackend`を`AUTHENTICATION_BACKENDS`に追加します。
```python
AUTHENTICATION_BACKENDS = [
    'graphql_jwt.backends.JSONWebTokenBackend',
    'django.contrib.auth.backends.ModelBackend',
]
```

##### 4.`RefreshTokenConfig`と`corsheaders`を`INSTALLED_APPS`に追加します。
`RefreshTokenConfig`は、リフレッシュトークンをDBに保存するために、追加します。
追加後は、`python manage.py migrate`を実行してください。
`refresh_token_refreshtoken`テーブルが作成されます。

`corsheaders`はCORSの設定のため、追加します。

```python
INSTALLED_APPS = [
    'corsheaders',
    'graphql_jwt.refresh_token.apps.RefreshTokenConfig',
]
```

##### 5.トークンの各種設定を追加します。
`JWT_VERIFY_EXPIRATION`・・・有効期限を確認するように設定します。
`JWT_LONG_RUNNING_REFRESH_TOKEN`・・・長期間有効なリフレッシュトークンを有効にします。
`JWT_EXPIRATION_DELTA`・・・トークンの有効期限を５分に設定します。
`JWT_REFRESH_EXPIRATION_DELTA`・・・リフレッシュトークンの有効期限を７日に設定します。

```python
GRAPHQL_JWT = {
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LONG_RUNNING_REFRESH_TOKEN': True,
    'JWT_EXPIRATION_DELTA': timedelta(minutes=5),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}
```
##### 6.CORSの設定を追加します。
ローカルホストの3000番ポートからのアクセスを許可します。

```python
CORS_ORIGIN_WHITELIST = [
    "http://localhost:3000",
]
```

## ルートスキーマの修正
プロジェクトフォルダ（config）に、`schema.py`を作成してjwt認証の`Mutaion`を追加します。

```python
import graphene
import graphql_jwt

class Mutation(graphene.ObjectType):
    # トークン生成
    token_auth = graphql_jwt.ObtainJSONWebToken.Field()
    # トークン認証
    verify_token = graphql_jwt.Verify.Field()
    # トークンリフレッシュ
    refresh_token = graphql_jwt.Refresh.Field()
    # リフレッシュトークンの無効化
    revoke_token = graphql_jwt.Revoke.Field()

    # リフレッシュトークンを一度使用したら削除する設定を追加
    @receiver(refresh_token_rotated)
    def revoke_refresh_token(sender, request, refresh_token, **kwargs):
        refresh_token.revoke(request)

schema = graphene.Schema(mutation=Mutation)
```

## エンドポイントの作成
`urls.py`を修正し、GraphQLのエンドポイントを作成します。
Django以外からアクセスしたときに、`CSRF`のエラーが出るため`csrf_exempt`で機能を解除します。

```python
from graphene_django.views import GraphQLView
from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True)))
]
```

## サンプルクエリ
設定は以上です。
参考に、各ユースケースのクエリをサンプルとして載せておきます。
クエリを実行したときに、payloadの取得やリフレッシュトークンのテーブルへの保存、無効化などがわかるかと思います。

- トークン生成

```graphql
mutation TokenAuth($username: String!, $password: String!) {
  tokenAuth(username: $username, password: $password) {
    token
    payload
    refreshToken
    refreshExpiresIn
  }
}
```

- トークン認証

```graphql
mutation VerifyToken($token: String!) {
  verifyToken(token: $token) {
    payload
  }
}
```

- トークンリフレッシュ

```graphql
mutation RefreshToken($refreshToken: String!) {
    refreshToken(refreshToken: $refreshToken) {
        token
        payload
        refreshToken
        refreshExpiresIn
    }
}
```

- リフレッシュトークンの無効化

```graphql
mutation RevokeToken($refreshToken: String!) {
    revokeToken(refreshToken: $refreshToken) {
        revoked
    }
}
```

## 最後に
今回はDjangoを利用してGraphQLのJWT認証を構築しました。
ライブラリを使うと簡単に構築できるので良いですね。

今回構築したJWT認証をフロントエンドのReactから使うことをこれから構築していく予定です。
構築できたらまた紹介できたらと思います。

## 参考サイト
https://django-graphql-jwt.domake.io/en/latest/index.html