Django REST frameworkでトークン認証をする
- # Python
- # Django
Django REST framework でトークン認証をするメモ
トークンの作成と取得
まずは, INSTALLED_APPS
に rest_framework.authtoken
を追加しておく
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken', # <= 追加
'api'
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
これでトークン用モデルが定義されたので、マイグレーションを実行する
$ python manage.py makemigrations && python manage.py migrate
トークンを取得する API エンドポイントの作成
rest_framework.authtoken
にトークン取得用の view が定義されているのでルーティングをつけてあげる
import rest_framework.authtoken.views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
path('api-token-auth/', auth_views.obtain_auth_token) # <= 追加
]
トークンの作成
引数に User モデルのインスタンスを渡して、普通に作れば OK
from rest_framework.authtoken.models import Token
Token.objects.create(
user=user # userは User モデルインスタンス
)
ユーザーが作成されると同時にトークンも自動生成するようにすることが望ましいので、ユーザーモデルをカスタマイズして自動的にトークンを作るようにしておく
カスタムユーザーの定義
トークンと紐付けるカスタムユーザーを, AbstarctBaseUser
から定義していく
トークン認証に使うユーザーモデルは, settings.py
で
# config/settings.py
AUTH_USER_MODEL = 'api.User'
と指定しておく必要がある
ただこうすると、管理ページへのログインもこの User を使うようになるので、 superuser 関連の設定もしておく
カスタムユーザーモデル
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.utils.translation import gettext_lazy as _
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
from typing import Any, Optional
from django.conf import settings
# ユーザーを作成した後に実行される
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender: str, instance: Optional['User'] = None, created: bool = False, **kwargs: Any) -> None:
if created and instance is not None:
# トークンの作成と紐付け
Token.objects.create(user=instance)
class UserManager(BaseUserManager):
def create_user(self, email: str, password: str) -> 'User':
user = User(
email=BaseUserManager.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
# トークンの作成
Token.objects.create(user=user)
return user
def create_superuser(self, email: str, password: str) -> 'User':
u = self.create_user(email=email,
password=password)
u.is_staff = True
u.is_superuser = True
u.save(using=self._db)
return u
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
unique=True,
blank=False
)
password = models.CharField(
_('password'),
max_length=128
)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
あとは普通に、serializer
と view
とルーティングを書く
from api.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('pk', 'email', 'password')
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
return User.create_user(
email=validated_data['email'],
password=validated_data['password']
)
from api.serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
from rest_framework import routers
from .views import UserViewSet
router = routers.DefaultRouter()
router.register('users', UserViewSet)
これで完成
マイグレーションすれば、
$ python manage.py makemigrations && python manage.py migrate
また、既存のユーザーには
$ python manage.py shell
In [1]: from api.models import User
In [2]: from rest_framework.authtoken.models import Token
In [3]: for user in User.objects.all():
try:
Token.objects.create(
user=user
)
except Exception:
pass
こんな感じで付与できる
トークンの取得
開発サーバーを建てた状態で, httpie で API を叩いてみる
$ http POST http://127.0.0.1:8000/api-token-auth/ username=[email protected] password=hoge
HTTP/1.1 200 OK
Allow: POST, OPTIONS
Content-Length: 52
Content-Type: application/json
Date: Mon, 25 May 2020 03:55:24 GMT
Server: WSGIServer/0.2 CPython/3.7.2
Vary: Cookie
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
ここで、username
は、User モデルにて USERNAME_FIELD
に規定したもの
class User(AbstractBaseUser, PermissionsMixin):
...
USERNAME_FIELD = 'email'
...
トークン認証
まだトークン認証はできるようになったが、各エンドポイントには認証なしでアクセスできるという状態なので、パーミッションクラスを書き換えておく
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
基本的にはトークン認証だけで良いが、DRF では各エンドポイントにアクセスしたときに API リファレンス(っぽいもの)が見れるので、セッションベースの認証を追加しておくとフロントエンド開発がしやすい(API の各エンドポイントをブラウザで叩くことで実際のレスポンスが見られる)
これで, 全ての view
のパーミッションがデフォルトが認証済みユーザー指定になった
認証いらずの View
は、
from rest_framework.permissions import AllowAny
class HogeViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny]
のように各ビューで上書きできる