概要
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
カスタムユーザーを定義します。
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を編集します。
...
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です。
ここで、Tokensを開き、先ほど作成したスーパーユーザーのトークンが作成されていることを確認できます。
続いて、serializers.pyを作成します。
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
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を作成し、以下の内容を書きます。
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アプリのパスを追加します。
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にて、作成したスーパーユーザーのトークンの取得を試してみます。
上図のように、
- POSTリクエスト
-
/api-token-auth
エンドポイント - リクエストボディにユーザー名とパスワード
を設定します。
そしてSendボタンによりリクエストをすると、下図のようにトークンが取得できるはずです。
続いて、受け取ったトークンを利用して、ユーザー一覧を取得するAPIを実行したいと思います。
まずは、トークン認証をせず、単に/api/v1/account
にGETリクエストを実行します。
すると、レスポンスは401 Unauthorizedで、
{
"detail": "Authentication credentials were not provided."
}
が返ってきました。
リクエストにトークンが設定されていないため、失敗しています。
続いて、リクエストヘッダーにトークンを設定します。
KEYにAuthorization
、VALUEにToken <トークン>
を設定してください。
トークン認証が通り、ユーザー情報が取得できるようになりました🎉
Dockerによる起動
この環境をdockerで起動するようにします。
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ファイルを作成し、以下を記載します。
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です🙆♂️
関連記事