読者です 読者をやめる 読者になる 読者になる

らっちゃいブログ

日々の学びと気づきを発信するブログ

django-rest-framework-jwtの認証をカスタマイズする方法

django rest_framework

スポンサーリンク

django-rest-framework-jwt については以前こちらの記事でご紹介しました。

racchai.hatenablog.com

django-rest-framework-jwt では認証APIを標準で用意してくれているので、通常はそれを使うことになります。

ですが、論理削除されたユーザーの場合は認証を失敗させたりだとか、認証処理をカスタマイズしたい場合は自前で認証してアクセストークンを返す処理を用意する必要があります。

本記事では、企業コード/メールアドレス/パスワードにて認証するケースを想定したカスタマイズ方法についてご紹介します。

Django および Django REST framework が初めてという方はこちらをどうぞ!

racchai.hatenablog.com

racchai.hatenablog.com

では、さっそく始めます。

プロジェクトを作成する

さくっと作成していきます。

$ django-admin startproject racchai
$ cd racchai

settings.py にて INSTALLED_APPSDATABASES を編集。

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'rest_framework',
+   'rest_framework_jwt',
+   'racchai',
)

DATABASES = { 
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'racchai',
        'USER': 'root',
        'PASSWORD': 'root',
        'HOST': 'localhost',
    }   
}

ついでにMySQLにデータベースを作成しておきましょう。

$ mysql -uroot -p -e "CREATE DATABASE racchai";

はい、以上です。

認証モデルを定義する

Django のユーザーテーブルには企業コードは存在しませんので、追加しておく必要があります。

ユーザーテーブルのカス高い図については、ここでは詳しい解説は割愛します。よくわからないという方はこちらをご参照ください。

racchai.hatenablog.com

今回は models.py を以下のように作成するだけでひとまずはOKです。

from django.db import models    
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

class RacchaiUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('Users must have a email address')

        email = RacchaiUserManager.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):
        return self.create_user(email, password)

class RacchaiUser(AbstractBaseUser):
    email = models.EmailField(max_length=128, unique=True)
    company = models.CharField(max_length=128, null=True)

    USERNAME_FIELD = 'email'

    objects = RacchaiUserManager()

    class Meta:
        db_table = 'racchai_user'
        swappable = 'AUTH_USER_MODEL'

本当は ./manage.py createsuperuser で企業コードも設定できるようにしたいところですが、今回はそこまではやりません。

RacchaiUserを作成したら、settings.py に以下を追記して認証モデルを差し替えておきます。

AUTH_USER_MODEL = 'racchai.RacchaiUser'

最後にマイグレーションを実行したら完了です。

$ ./manage.py migrate

念のためテーブル定義を確認。問題なさそうですね。

mysql> desc racchai_user;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| password   | varchar(128) | NO   |     | NULL    |                |
| last_login | datetime(6)  | YES  |     | NULL    |                |
| email      | varchar(128) | NO   | UNI | NULL    |                |
| company    | varchar(128) | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+

Serializerを定義する

次はAPIの入力情報を扱うSerializerを定義します。今回は入力値が3つあるので、以下のようになります。

jwt_auth.py

from rest_framework import serializers

class AuthInputSerializer(serializers.Serializer):
    company = serializers.CharField()
    email = serializers.EmailField()
    password = serializers.CharField()

View を定義する

最後に API 本体の実装です。さきほど Serializer を定義した jwt_auth.py に以下を追記しましょう。

from django.contrib.auth import authenticate
from rest_framework_jwt.settings import api_settings
from rest_framework.generics import GenericAPIView
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.response import Response

jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

class AuthView(GenericAPIView):
    serializer_class = AuthInputSerializer
    def post(self, request):
        serializer = AuthInputSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = authenticate(**serializer.data)
        if not user:
            raise AuthenticationFailed()
        # 以下2行が今回のカスタマイズ部分
        if user.company != serializer.data['company']:
            raise AuthenticationFailed()
        payload = jwt_payload_handler(user)
        return Response({
            'token': jwt_encode_handler(payload),
        })

最後に urls.py に以下を追記して /jwt_auth でアクセスできるようにします。

from racchai.jwt_auth import AuthView

urlpatterns = [
    url(r'^jwt_auth', AuthView.as_view()),
]

これで認証ができるようになったはずです。

試してみる

API を試す前に、まだユーザーを登録していなかったので先にユーザーを作成しておきます。

$ ./manage.py createsuperuser
Email: jwt@racchai.com
Password: jwt
Password (again): jwt
Superuser created successfully.

前述の通り、この時点ではユーザーレコードに会社コードが設定されていませんので、SQL で直接会社コードを設定してしまいます。会社コードは仮に0001としておきます。

$  mysql -uroot -p racchai -e "update racchai_user set company = '0001'"

準備が整ったところで、開発サーバーを起動して認証APIを実行してみます。

$ ./manage.py runserver
....

$ curl http://localhost:8000/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0001"
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imp3dEByYWNjaGFpLmNvbSIsInVzZXJfaWQiOjIsImVtYWlsIjoiand0QHJhY2NoYWkuY29tIiwiZXhwIjoxNDYzNjI1NDQ5fQ.YX6a_9680xDDYOiqwXX1kAZA1dMH3rLIXq7oGTaOawc"}

無事JWTアクセストークンを取得できましたね!

念のため、会社コードを間違えたらどうなるのか見ておくことにします。

$ curl http://localhost:8888/jwt_auth -d "email=jwt@racchai.com&password=jwt&company=0002"
{"detail":"Incorrect authentication credentials."}

ちゃんと認証に失敗することも確認できましたね。

まとめ

だいぶ早足でしたが、カスタマイズ自体は難しいことではないってことだけ伝われば幸いです!