LoginSignup
4
3

More than 1 year has passed since last update.

DRFの認証処理(TokenAuthentication)

Posted at

概要

Django REST Frameworkのトークン認証の方法についてまとめました。

Django REST Framework

Token Authenticationのドキュメントは以下

トークン認証とは?

トークン認証とは、ユーザーがユーザーIDとパスワードなどの自身の情報をリクエストすることで、アクセストークンを受け取り、その後のリクエストは受け取ったトークンを使用し、トークンの有効期間内はアクセスを可能とするような認証方法です。

GitHub

今回解説するサンプルソースは、GitHubにて公開しています。

環境

各種バージョン

  • macOS Monterey 12.4
  • Apple M1 Max
  • Python v3.10.9
  • Django v4.1.4
  • djangorestframework v3.14.0

ディレクトリ構成

drf-authentication-demo/
 ├build/
 │ └Dockerfile
 ├projects/
 │ └token_auth/
 ├venv/
 ├docker-compose.yml 
 └requirements.txt

実践

仮想環境構築

仮想環境を構築し、アクティベートします。

drf-authentication-demo % python -m venv venv
drf-authentication-demo % source venv/bin/activate

requirements.txtを作成し、以下の内容を記載します。

Django==4.1.4
djangorestframework==3.14.0

必要なパッケージをインストールします。

(venv) drf-authentication-demo % pip install -r requirements.txt

Djangoプロジェクト作成

projectsディレクトリにtoken_authディレクトリを生成し、token_authディレクトリ以下でDjango プロジェクトを作成します。

(venv) drf-authentication-demo % cd projects/token_auth
(venv) token_auth % django-admin startproject project .

一度、開発サーバーを起動し、デフォルト画面が表示できるかを確認すると良いでしょう。以下のコマンドを実行後、ブラウザでhttp://127.0.0.1:8000にアクセスし、Djangoのデフォルト画面が表示されればOKです。

(venv) token_auth % python manage.py runserver

アプリ作成

トークン認証を用いてユーザー情報を取得するアプリを作成します。

(venv) token_auth % python manage.py startapp account

カスタムユーザーを定義します。

token_auth/account/models.py
from django.db import models
from django.contrib.auth.models import (BaseUserManager,
                                        AbstractBaseUser,
                                        PermissionsMixin)
from django.utils.translation import gettext_lazy as _

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token


@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created and instance is not None:
        Token.objects.create(user=instance)


class UserManager(BaseUserManager):
    def _create_user(self, email, username, password, **extra_fields):
        email = self.normalize_email(email)
        user = self.model(email=email, username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        user.full_clean()

        Token.objects.create(user=user)

        return user

    def create_user(self, email, username, password=None, **extra_fields):
        extra_fields.setdefault('is_active', True)
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(
            email=email,
            username=username,
            password=password,
            **extra_fields,
        )

    def create_superuser(self, email, username, password, **extra_fields):
        extra_fields['is_active'] = True
        extra_fields['is_staff'] = True
        extra_fields['is_superuser'] = True
        return self._create_user(
            email=email,
            username=username,
            password=password,
            **extra_fields,
        )


class User(AbstractBaseUser, PermissionsMixin):

    username = models.CharField(
        verbose_name=_("username"),
        unique=True,
        max_length=150
    )
    email = models.EmailField(
        verbose_name=_("Email Address"),
        unique=True
    )
    age = models.IntegerField(
        verbose_name=_("age"),
        null=True,
        blank=True
    )
    is_superuser = models.BooleanField(
        verbose_name=_("is_superuer"),
        default=False
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
    )

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def __str__(self):
        return self.username

models.pyについて解説します。

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created and instance is not None:
        Token.objects.create(user=instance)

ユーザーを作成すると同時にトークン認証用のトークンを作成する処理です。

class User(AbstractBaseUser, PermissionsMixin):

    username = models.CharField(
        verbose_name=_("username"),
        unique=True,
        max_length=150
    )
    email = models.EmailField(
        verbose_name=_("Email Address"),
        unique=True
    )
    age = models.IntegerField(
        verbose_name=_("age"),
        null=True,
        blank=True
    )
    is_superuser = models.BooleanField(
        verbose_name=_("is_superuer"),
        default=False
    )
    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
    )

    objects = UserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    def __str__(self):
        return self.username

カスタムユーザーを定義します。

class UserManager(BaseUserManager):
    def _create_user(self, email, username, password, **extra_fields):
        email = self.normalize_email(email)
        user = self.model(email=email, username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        user.full_clean()

        return user

    def create_user(self, email, username, password=None, **extra_fields):
        extra_fields.setdefault('is_active', True)
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(
            email=email,
            username=username,
            password=password,
            **extra_fields,
        )

    def create_superuser(self, email, username, password, **extra_fields):
        extra_fields['is_active'] = True
        extra_fields['is_staff'] = True
        extra_fields['is_superuser'] = True
        return self._create_user(
            email=email,
            username=username,
            password=password,
            **extra_fields,
        )

カスタムユーザーを作成する際は、ユーザーマネージャーの方もカスタマイズする必要があります。

ここではユーザーやスーパーユーザーの作成の際に呼び出されるメソッドを定義します。

settings.pyを編集します。

token_auth/project/settings.py
...

ALLOWED_HOSTS = ["*"] # ワイルドカードを追加

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # djangorestframeworkを追加
    'rest_framework.authtoken', # token認証用

    'account' # 作成したaccountアプリを追加
]

...

AUTH_USER_MODEL = "account.User" # accountアプリのUserモデルをデフォルトで使用する認証ユーザーモデルとして設定する

ここまでできましたら、マイグレートします。

(venv) token_auth % python manage.py makemigrations
(venv) token_auth % python manage.py migrate

ここでスーパーユーザーを作成し、管理画面にログインできるか確認します。任意のユーザー情報で作成してください。

(venv) token_auth % python manage.py createsuperuser
Username (leave blank to use 'testuser'): testuser
Email address: test@example.com
Password: 
Password (again):
Superuser created successfully.

管理画面にログインできるか確認するため、開発サーバーを起動します。

(venv) token_auth % python manage.py runserver

ブラウザでhttp://127.0.0.1:8000/admin/にアクセスし、ログインフォームから作成したスーパーユーザー情報を用いてログインできればOKです。

ログイン画面
Screen Shot 2022-12-28 at 5.15.58.png

ログイン後の画面
Screen Shot 2023-01-12 at 20.40.47.png

ここで、Tokensを開き、先ほど作成したスーパーユーザーのトークンが作成されていることを確認できます。

Screen Shot 2023-01-12 at 20.55.09.png

続いて、serializers.pyを作成します。

token_auth/account/serializers.py
from rest_framework import serializers
from .models import User


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = "__all__"

views.pyを編集します。

token_auth/account/views.pyを
from rest_framework import viewsets
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

from .models import User
from .serializers import UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = (TokenAuthentication,)
    permission_classes = (IsAuthenticated, )

UserViewSetのauthentication_classesにTokenAuthenticationを、

permission_classesにIsAuthenticatedを設定します。

これにより、このViewSetはトークン認証が必須となります。

accountアプリでurls.pyを作成し、以下の内容を書きます。

token_auth/account/urls.py
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import UserViewSet

router = DefaultRouter()
router.register(r'account', UserViewSet)

urlpatterns = [
    path('', include(router.urls))
]

project/urls.pyにaccountアプリのパスを追加します。

token_auth/project/urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/', include('account.urls')),
    path('api-token-auth/', views.obtain_auth_token),
]

api-token-auth/は、クライアントがトークンを取得するためのエンドポイントになります。

ここまでできましたら、トークン認証により、ユーザー情報を取得するAPIを実行します。

今回は、Postmanを利用して動作確認します。
Postmanをダウンロードしていない方は、以下のサイトよりダウンロードしてください。

Postmanにて、作成したスーパーユーザーのトークンの取得を試してみます。

Screen Shot 2023-01-12 at 21.00.29.png

上図のように、

  • POSTリクエスト
  • /api-token-authエンドポイント
  • リクエストボディにユーザー名とパスワード

を設定します。

そしてSendボタンによりリクエストをすると、下図のようにトークンが取得できるはずです。

Screen Shot 2023-01-12 at 21.05.02.png

続いて、受け取ったトークンを利用して、ユーザー一覧を取得するAPIを実行したいと思います。

まずは、トークン認証をせず、単に/api/v1/accountにGETリクエストを実行します。
Screen Shot 2023-01-12 at 21.06.02.png

すると、レスポンスは401 Unauthorizedで、

{
    "detail": "Authentication credentials were not provided."
}

が返ってきました。

リクエストにトークンが設定されていないため、失敗しています。

続いて、リクエストヘッダーにトークンを設定します。Screen Shot 2023-01-12 at 21.10.38.png
KEYにAuthorization、VALUEにToken <トークン>を設定してください。

そしてSendボタンで実行すると、
Screen Shot 2023-01-12 at 21.11.42.png

トークン認証が通り、ユーザー情報が取得できるようになりました🎉

Dockerによる起動

この環境をdockerで起動するようにします。
Dockerfileを作成し、以下を記載します。

build/Dockerfile
FROM python:3.10-buster

ENV PYTHONUNBUFFERED 1

WORKDIR /app
ADD requirements.txt /app/
RUN pip install --upgrade pip \
  && pip install -r requirements.txt

ADD ./projects /app/

docker-compose.ymlファイルを作成し、以下を記載します。

docker-compose.yml
version: '3'

services:
  token:
    container_name: token-auth-app
    restart: always
    build:
      context: .
      dockerfile: ./build/Dockerfile
    command: python3 token_auth/manage.py runserver 0.0.0.0:8000
    volumes:
      - ./projects:/app
    ports:
      - "8002:8000"
    tty: true

以下のコマンドを実行して、起動します。

% docker-compose up --build

Postmanでhttp://127.0.0.1:8002/api/v1/account/にリクエストし、レスポンスが返ってくればOKです🙆‍♂️

関連記事

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3