5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Django-rest-frameworkでbasic認証 + テストコード

Posted at

django-rest-frameworkでBasic認証を実装、そのテストコードを記述する。

  • python 3.7.4
  • django 3.0.3
  • django-rest-framework 3.11.0

basic認証の実装

genericViewでも、views.APIViewでも
authentication_classesおよびpermission_classesを指定すれば、認証を行ってくれる。
認証が通るとrequest.userに認証したユーザーインスタンスを、失敗すると401レスポンスを返してくれる。

views.py
# DRF
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import BasicAuthentication
from rest_framework.response import Response
from rest_framework import status


class UserInfoAPI(APIView):
    """ユーザー情報取得API"""
    authentication_classes = (BasicAuthentication,)
    permission_classes = (IsAuthenticated,)

    def get(self, request):
        # 認証情報は、request.userに格納される
        user = request.user
        data = {
            "message": "Token valid.",
            "full_name": user.full_name,
            "profile": user.profile if user.profile else None
        }
        response = Response(data, status=status.HTTP_200_OK)
        return response

test

テストユーザー作成時にトークンを作成しておく。
self.client.credentialsにHTTP_AUTHORIZATIONという引数でヘッダー文字列を渡す。
メールとパスワードの平文をエンコード、その文字列をbase64でエンコード、最後にそれをデコードしてやる。

tests.py
import base64
from rest_framework import HTTP_HEADER_ENCODING
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from app.models import User

class TestAuthAPI(APITestCase):
    def setUp(self):
        mail = "test@sample.com"
        password = "testPass"
        self.user = User.objects.create(email=mail,
                                        password=password)
        self.credentials = base64.b64encode(
                f'{email}:{password}'.encode(HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)
        self.url = reverse("app:auth")

    def test_success(self):
        self.client.credentials(HTTP_AUTHORIZATION=f'Basic {self.credential}')
        request = self.client.get(self.url)
        self.assertEqual(request.status_code, 200)
        self.assertEqual(request.json()["full_name"], self.user.full_name)

UserModelについて

認証するUserモデルは、AbstractBaseUserを継承していなくてはならない。
BasicAuthenticationクラスは、User.is_activeというプロパティを参照するようになっている。
またusernameではなくEmail+Passwordを使用する場合、USERNAME_FIELD="email"と設定しておく。
BasicAuthenticationクラスがUSERNAME_FIELDを参照し、ユーザーを特定するからである。

以下がBasicAuthenticationクラスである。

rest-framework/authentication.py
class BasicAuthentication(BaseAuthentication):
    """
    HTTP Basic authentication against username/password.
    """
    www_authenticate_realm = 'api'

    def authenticate(self, request):
        # ヘッダーから文字列を取り出す処理・・・
        return self.authenticate_credentials(userid, password, request)

    def authenticate_credentials(self, userid, password, request=None):
        """
        Authenticate the userid and password against username and password
        with optional request for context.
        """
        credentials = {
            get_user_model().USERNAME_FIELD: userid, # USERNAME_FIELDを参照する
            'password': password
        }
        user = authenticate(request=request, **credentials)

        if user is None:
            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))
        # user.is_activeがFalseの場合、401
        if not user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (user, None)

    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm
5
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?