プログラミング

【Python】DjangoでGraphQLでのjwt認証を行う

解説は上記記事で構築した環境をもとに行いますので、プロジェクトやアプリケーションのディレクトリ、docker関連のコマンドなどは適宜読み替えてください。

プロジェクト ... /project

アプリケーション ... /app

環境

OS ... macOS Ventura 13.1

エディタ ... VSCode

ライブラリ

Django==4.1.7
graphene_django==3.0.0
django-graphql-jwt==0.3.4
django-cors-headers==3.14.0

セットアップ

settings.pyに設定情報を追加

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

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
]

AUTHENTICATION_BACKENDS = [
    'graphql_jwt.backends.JSONWebTokenBackend',
    'django.contrib.auth.backends.ModelBackend',
]

GRAPHENE = {
    'SCHEMA': 'project.schema.schema',
    'MIDDLEWARE': [
        'graphql_jwt.middleware.JSONWebTokenMiddleware',
    ],
}

GRAPHQL_JWT = {
    'JWT_VERIFY_EXPIRATION': True, # tokenの有効期限を確認する
    'JWT_LONG_RUNNING_REFRESH_TOKEN': True, # リフレッシュトークンを有効化
    'JWT_EXPIRATION_DELTA': timedelta(minutes=10), # tokenの有効期限
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7), refreshTokenの有効期限
}

CORS_ORIGIN_WHITELIST = [
    "http://localhost:8000", CORSでアクセスを許可するURL
]

追加後にpython manage.py migrateを実行してください。

タイプを定義

appのtypes.pyに、ユーザーに関するタイプを追加します。

from graphene_django.types import DjangoObjectType
from django.contrib.auth import get_user_model

class UserType(DjangoObjectType):
    class Meta:
        model = get_user_model()

スキーマを定義

appのscheme.pyに、認証に関する処理を追加します。

import graphene
from app.models import Fruit
from .types import UserType, FruitType
from django.contrib.auth import get_user_model
from graphql_jwt.decorators import login_required
import graphql_jwt

User = get_user_model()

class Query:
    current_user = graphene.Field(UserType)
    all_users = graphene.List(UserType)

    def resolve_current_user(root, info):
        user = info.context.user
        return user

    # ログインしている場合のみ呼び出し可能
    @login_required
    def resolve_all_users(root, info):
        return User.objects.all()

class CreateUserMutation(graphene.Mutation):
    success = graphene.Boolean()
    message = graphene.String()
    user = graphene.Field(UserType)

    class Arguments:
        username = graphene.String(required=True)
        email = graphene.String(required=True)
        password1 = graphene.String(required=True)
        password2 = graphene.String(required=True)

    def mutate(self, info, username, email, password1, password2):
        existsUsername = User.objects.filter(username=username).exists()
        if existsUsername:
            return CreateUserMutation(success=False, message="登録済みのユーザーIDです", user=None)

        existsEmail = User.objects.filter(email=email).exists()
        if existsEmail:
            return CreateUserMutation(success=False, message="登録済みのメールアドレスです", user=None)

        errorPassword = password1 != password2
        if errorPassword:
            return CreateUserMutation(success=False, message="パスワードが一致していません", user=None)

        user = get_user_model()(
            username=username,
            email=email,
        )

        user.set_password(password1)
        user.save()

        return CreateUserMutation(success=True, message="アカウント登録に成功しました", user=user)

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

エンドポイントの追加

GraphQLのエンドポイントを追加します。

今回はAltairからアクセスを行いたいため、csrf_exemptを用いてCSRFのエラーが発生しないようにします。

from django.urls import path
from graphene_django.views import GraphQLView
from project.scheme import schema
from django.views.decorators.csrf import csrf_exempt

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

動作確認

ここまでで設定は完了したので、動作確認を行います。

アカウント作成

mutation {
  createUser(
    username: "sample"
    email: "sample@gmail.com"
    password1: "sample1234"
    password2: "sample1234"
  ) {
    success
    message
    user {
      id
      username
      email
    }
  }
}

登録が成功すると、登録したユーザーの情報がレスポンスされます。

登録済みの情報やパスワードが一致していない場合、エラーが返却されます。

トークン生成

mutation {
  tokenAuth(username: "sample", password: "sample1234") {
    payload
    refreshExpiresIn
    token
    refreshToken
  }
}

先程登録したアカウントを用いてトークン生成を行います。

レスポンスされたtokenをヘッダーにセットしてリクエストを行うことで、サーバに認証済みのアカウントからのリクエストであると認識されます。

ヘッダー情報

  • Key ... Authorization
  • value ... JWT [token] (例: tokenが 1q2w3e4r の場合 JWT 1q2w3e4r)

現在のユーザーを取得

query {
  currentUser {
    id
    username
    email
  }
}

ヘッダーに先程取得したtokenをセットしてリクエストすると、ユーザー情報が返却されます。

tokenがセットされていなかったり間違っていたりすると、エラーが返却されます。

ユーザー一覧を取得する

query {
  allUsers {
    id
    username
    email
  }
}

allUsersは @login_requiredデコレーターがついているため、ヘッダーにtokenがセットされている場合ユーザー一覧がレスポンスされます。

トークン認証

mutation {
  verifyToken(token: [token]) {
    payload
  }
}

トークン再生成

mutation {
  refreshToken(refreshToken: [refreshToken]) {
    payload
    refreshExpiresIn
    token
    refreshToken
  }
}

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

mutation {
  revokeToken(refreshToken: [refreshToken]) {
    revoked
  }
}

  • この記事を書いた人
  • 最新記事

おみ

プログラミング学習やキャリアのことを発信していきます。【経歴】1999年生まれ。専門学校卒業後、大手企業やベンチャー企業でSEとして勤務。現在は某メガベンチャーでFlutterエンジニアとして働いています。

-プログラミング
-, , ,