authentication.py

認証

認証はプラグイン可能である必要があります。

— Jacob Kaplan-Moss, "RESTワーストプラクティス"

認証とは、受信リクエストと一意の認証情報(リクエストの発信元ユーザーや、リクエストの署名に使用されたトークンなど)を関連付けるメカニズムです。その後、パーミッションスロットリングポリシーは、これらの認証情報を使用して、リクエストを許可するかどうかを判断します。

REST frameworkは、すぐに使用できる複数の認証スキームを提供し、カスタムスキームを実装することもできます。

認証は、常にビューの開始時に、パーミッションとスロットリングのチェック、およびその他のコードの実行の前に実行されます。

request.userプロパティは、通常、contrib.authパッケージのUserクラスのインスタンスに設定されます。

request.authプロパティは、追加の認証情報に使用されます。たとえば、リクエストの署名に使用された認証トークンを表すために使用できます。


注記: 認証だけでは、受信リクエストを許可または拒否しません。単に、リクエストが行われた際の認証情報を識別するだけです。

APIのパーミッションポリシーの設定方法については、パーミッションのドキュメントを参照してください。


認証の判定方法

認証スキームは常にクラスのリストとして定義されます。REST frameworkは、リスト内の各クラスで認証を試行し、最初に正常に認証されたクラスの戻り値を使用してrequest.userrequest.authを設定します。

クラスが認証に失敗した場合、request.userdjango.contrib.auth.models.AnonymousUserのインスタンスに設定され、request.authNoneに設定されます。

認証されていないリクエストのrequest.userrequest.authの値は、UNAUTHENTICATED_USERUNAUTHENTICATED_TOKEN設定を使用して変更できます。

認証スキームの設定

デフォルトの認証スキームは、DEFAULT_AUTHENTICATION_CLASSES設定を使用してグローバルに設定できます。例:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ]
}

APIViewクラスベースビューを使用して、ビューごとまたはビューセットごとに認証スキームを設定することもできます。

from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'user': str(request.user),  # `django.contrib.auth.User` instance.
            'auth': str(request.auth),  # None
        }
        return Response(content)

または、関数ベースビューで@api_viewデコレータを使用している場合。

@api_view(['GET'])
@authentication_classes([SessionAuthentication, BasicAuthentication])
@permission_classes([IsAuthenticated])
def example_view(request, format=None):
    content = {
        'user': str(request.user),  # `django.contrib.auth.User` instance.
        'auth': str(request.auth),  # None
    }
    return Response(content)

未認証と許可拒否のレスポンス

認証されていないリクエストがパーミッションを拒否された場合、適切な2つの異なるエラーコードがあります。

HTTP 401レスポンスには、常にWWW-Authenticateヘッダーを含める必要があります。これは、クライアントに認証方法を指示します。HTTP 403レスポンスには、WWW-Authenticateヘッダーは含まれません。

使用されるレスポンスの種類は、認証スキームによって異なります。複数の認証スキームが使用されている場合でも、レスポンスの種類を決定するために使用できるスキームは1つだけです。 **ビューに設定されている最初の認証クラスが、レスポンスの種類を決定する際に使用されます。**

リクエストが正常に認証された場合でも、リクエストの実行に対するパーミッションが拒否される可能性があります。その場合、認証スキームに関係なく、常に403 許可拒否レスポンスが使用されます。

Apache mod_wsgi固有の設定

Apacheでmod_wsgiを使用している場合、認証ヘッダーはデフォルトではWSGIアプリケーションに渡されません。これは、認証がアプリケーションレベルではなくApacheによって処理されると想定されているためです。

Apacheにデプロイしていて、セッションベース以外の認証を使用している場合は、必要なヘッダーをアプリケーションに明示的に渡すようにmod_wsgiを設定する必要があります。これは、適切なコンテキストでWSGIPassAuthorizationディレクティブを指定し、それを'On'に設定することで実行できます。

# this can go in either server config, virtual host, directory or .htaccess
WSGIPassAuthorization On

APIリファレンス

BasicAuthentication

この認証スキームは、HTTP Basic Authenticationを使用して、ユーザーのユーザー名とパスワードに対して署名します。Basic認証は、一般的にテストにのみ適しています。

正常に認証された場合、BasicAuthenticationは次の認証情報を提供します。

  • request.userは、DjangoのUserインスタンスになります。
  • request.authNoneになります。

パーミッションが拒否された認証されていないレスポンスは、適切なWWW-Authenticateヘッダーを含むHTTP 401 未認証レスポンスになります。例:

WWW-Authenticate: Basic realm="api"

注記: 本番環境でBasicAuthenticationを使用する場合は、APIがhttps経由でのみ使用可能であることを確認する必要があります。また、APIクライアントがログイン時に常にユーザー名とパスワードを再要求し、これらの詳細を永続的なストレージに保存しないようにする必要があります。

TokenAuthentication


注記: Django REST frameworkによって提供されるトークン認証は、かなり単純な実装です。

ユーザーごとに複数のトークンを許可し、セキュリティ実装の詳細を強化し、トークンの有効期限をサポートする実装については、サードパーティパッケージのDjango REST Knoxを参照してください。


この認証スキームは、単純なトークンベースのHTTP認証スキームを使用します。トークン認証は、ネイティブデスクトップクライアントやモバイルクライアントなど、クライアントサーバーの設定に適しています。

TokenAuthenticationスキームを使用するには、認証クラスの設定TokenAuthenticationを含め、さらにINSTALLED_APPS設定にrest_framework.authtokenを含める必要があります。

INSTALLED_APPS = [
    ...
    'rest_framework.authtoken'
]

設定を変更した後、manage.py migrateを実行してください。

rest_framework.authtokenアプリは、Djangoデータベースのマイグレーションを提供します。

ユーザーのトークンを作成する必要もあります。

from rest_framework.authtoken.models import Token

token = Token.objects.create(user=...)
print(token.key)

クライアントが認証を行うには、トークンキーをAuthorizationHTTPヘッダーに含める必要があります。キーは文字列リテラル「Token」をプレフィックスとして、2つの文字列を空白で区切る必要があります。例:

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

ヘッダーでBearerなどの異なるキーワードを使用する場合は、単にTokenAuthenticationをサブクラス化し、keywordクラス変数を設定します。

正常に認証された場合、TokenAuthenticationは次の認証情報を提供します。

  • request.userは、DjangoのUserインスタンスになります。
  • request.authは、rest_framework.authtoken.models.Tokenインスタンスになります。

パーミッションが拒否された認証されていないレスポンスは、適切なWWW-Authenticateヘッダーを含むHTTP 401 未認証レスポンスになります。例:

WWW-Authenticate: Token

curlコマンドラインツールは、トークン認証されたAPIのテストに役立ちます。例:

curl -X GET http://127.0.0.1:8000/api/example/ -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'

注記: 本番環境でTokenAuthenticationを使用する場合は、APIがhttps経由でのみ使用可能であることを確認する必要があります。


トークンの生成

シグナルの使用

すべてのユーザーに自動的に生成されたトークンを持たせたい場合は、ユーザーのpost_saveシグナルをキャッチするだけです。

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:
        Token.objects.create(user=instance)

このコードスニペットをインストール済みのmodels.pyモジュールまたは起動時にDjangoによってインポートされるその他の場所に配置する必要があることに注意してください。

既にいくつかのユーザーを作成している場合は、次のようにして既存のすべてのユーザーのトークンを生成できます。

from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token

for user in User.objects.all():
    Token.objects.get_or_create(user=user)

APIエンドポイントの公開

TokenAuthenticationを使用する場合、ユーザー名とパスワードが与えられた場合にクライアントがトークンを取得するためのメカニズムを提供したい場合があります。REST frameworkは、この動作を提供する組み込みビューを提供します。これを使用するには、obtain_auth_tokenビューをURLconfに追加します。

from rest_framework.authtoken import views
urlpatterns += [
    path('api-token-auth/', views.obtain_auth_token)
]

パターンのURLの部分は、使用したいものにすることができます。

有効なusernamepasswordフィールドがフォームデータまたはJSONを使用してビューにPOSTされた場合、obtain_auth_tokenビューはJSONレスポンスを返します。

{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }

デフォルトのobtain_auth_tokenビューは、設定のデフォルトのレンダラーとパーサークラスを使用するのではなく、明示的にJSONリクエストとレスポンスを使用することに注意してください。

デフォルトでは、obtain_auth_tokenビューにはアクセス許可やスロットリングは適用されていません。スロットリングを適用する場合は、ビュークラスをオーバーライドし、throttle_classes属性を使用して含める必要があります。

obtain_auth_tokenビューのカスタマイズ版が必要な場合は、ObtainAuthTokenビュークラスをサブクラス化し、それをURLコンフィグで使用することで実現できます。

たとえば、token値以外にも追加のユーザー情報を返すことができます。

from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomAuthToken(ObtainAuthToken):

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data,
                                           context={'request': request})
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'email': user.email
        })

そして、あなたのurls.pyでは

urlpatterns += [
    path('api-token-auth/', CustomAuthToken.as_view())
]

Django管理画面を使用する場合

管理インターフェースから手動でトークンを作成することも可能です。ユーザーベースが大きい場合は、TokenAdminクラスをモンキーパッチしてニーズに合わせてカスタマイズすることをお勧めします。具体的には、userフィールドをraw_fieldとして宣言します。

your_app/admin.py:

from rest_framework.authtoken.admin import TokenAdmin

TokenAdmin.raw_id_fields = ['user']

Django manage.pyコマンドを使用する場合

バージョン3.6.4以降、次のコマンドを使用してユーザーのトークンを生成できます。

./manage.py drf_create_token <username>

このコマンドは、指定されたユーザーのAPIトークンを返し、存在しない場合は作成します。

Generated token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b for user user1

トークンを再生成する必要がある場合(たとえば、トークンが侵害または漏洩した場合)、追加のパラメータを渡すことができます。

./manage.py drf_create_token -r <username>

SessionAuthentication

この認証スキームは、認証にDjangoのデフォルトのセッションバックエンドを使用します。セッション認証は、Webサイトと同じセッションコンテキストで実行されているAJAXクライアントに適しています。

認証に成功した場合、SessionAuthenticationは次の資格情報を提供します。

  • request.userは、DjangoのUserインスタンスになります。
  • request.authNoneになります。

アクセス許可が拒否された未認証のレスポンスは、HTTP 403 Forbiddenレスポンスになります。

SessionAuthenticationを使用してAJAXスタイルのAPIを使用している場合、PUTPATCHPOSTDELETEリクエストなど、「安全ではない」HTTPメソッド呼び出しには有効なCSRFトークンを含める必要があります。詳細は、Django CSRFドキュメントを参照してください。

警告:ログインページを作成する際は、常にDjangoの標準的なログインビューを使用してください。これにより、ログインビューが適切に保護されます。

REST frameworkでのCSRF検証は、同じビューへのセッションベースと非セッションベースの両方の認証をサポートする必要があるため、標準的なDjangoとは多少異なります。これは、認証されたリクエストのみがCSRFトークンを必要とし、匿名のリクエストはCSRFトークンなしで送信できることを意味します。この動作は、常にCSRF検証が適用される必要があるログインビューには適していません。

RemoteUserAuthentication

この認証スキームを使用すると、REMOTE_USER環境変数を設定するWebサーバーに認証を委任できます。

これを使用するには、AUTHENTICATION_BACKENDS設定にdjango.contrib.auth.backends.RemoteUserBackend(またはサブクラス)を含める必要があります。デフォルトでは、RemoteUserBackendは、まだ存在しないユーザー名に対してUserオブジェクトを作成します。これと他の動作を変更するには、Djangoドキュメントを参照してください。

認証に成功した場合、RemoteUserAuthenticationは次の資格情報を提供します。

  • request.userは、DjangoのUserインスタンスになります。
  • request.authNoneになります。

認証方法の設定に関する情報は、Webサーバーのドキュメントを参照してください。例:

カスタム認証

カスタム認証スキームを実装するには、BaseAuthenticationをサブクラス化し、.authenticate(self, request)メソッドをオーバーライドします。このメソッドは、認証に成功した場合は(user, auth)の2要素タプルを、そうでない場合はNoneを返す必要があります。

状況によっては、Noneを返す代わりに、.authenticate()メソッドからAuthenticationFailed例外を発生させることができます。

一般的には、次のアプローチを取る必要があります。

  • 認証が試行されない場合は、Noneを返します。使用されている他の認証スキームも引き続きチェックされます。
  • 認証が試行されたが失敗した場合は、AuthenticationFailed例外を発生させます。エラーレスポンスは、アクセス許可チェックに関係なく、他の認証スキームをチェックすることなく、すぐに返されます。

.authenticate_header(self, request)メソッドをオーバーライドすることもできます。実装されている場合、HTTP 401 UnauthorizedレスポンスのWWW-Authenticateヘッダーの値として使用される文字列を返す必要があります。

.authenticate_header()メソッドがオーバーライドされていない場合、認証スキームは、未認証のリクエストでアクセスが拒否されたときにHTTP 403 Forbiddenレスポンスを返します。


注記:カスタム認証子がリクエストオブジェクトの.userまたは.authプロパティによって呼び出された場合、AttributeErrorWrappedAttributeErrorとして再発生することがあります。これは、元の例外が外部のプロパティアクセスによって抑制されるのを防ぐために必要です。Pythonは、AttributeErrorがカスタム認証子から発生したことを認識せず、代わりにリクエストオブジェクトに.userまたは.authプロパティがないと仮定します。これらのエラーは、認証者によって修正または処理される必要があります。


次の例では、'X-USERNAME'というカスタムリクエストヘッダー内のユーザー名によって指定されたユーザーとして、着信リクエストを認証します。

from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions

class ExampleAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):
        username = request.META.get('HTTP_X_USERNAME')
        if not username:
            return None

        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise exceptions.AuthenticationFailed('No such user')

        return (user, None)

サードパーティパッケージ

次のサードパーティパッケージも利用できます。

django-rest-knox

Django-rest-knoxライブラリは、組み込みのTokenAuthenticationスキームよりも安全で拡張可能な方法でトークンベースの認証を処理するためのモデルとビューを提供します。シングルページアプリケーションとモバイルクライアントを念頭に置いています。クライアントごとのトークンと、他の認証(通常は基本認証)を提供したときにそれらを生成するためのビュー、トークンを削除する(サーバー強制ログアウトを提供する)、すべてのトークンを削除する(ユーザーがログインしているすべてのクライアントをログアウトする)を提供します。

Django OAuth Toolkit

Django OAuth ToolkitパッケージはOAuth 2.0サポートを提供し、Python 3.4+で動作します。jazzbandによって保守されており、優れたOAuthLibを使用しています。このパッケージは、ドキュメントが充実しており、サポートも充実しており、現在OAuth 2.0サポートにお勧めのパッケージです。

インストールと設定

pipを使用してインストールします。

pip install django-oauth-toolkit

パッケージをINSTALLED_APPSに追加し、REST frameworkの設定を変更します。

INSTALLED_APPS = [
    ...
    'oauth2_provider',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ]
}

詳細は、Django REST framework - Getting startedドキュメントを参照してください。

Django REST framework OAuth

Django REST framework OAuthパッケージは、REST frameworkに対してOAuth1とOAuth2の両方のサポートを提供します。

このパッケージは以前はREST frameworkに直接含まれていましたが、現在はサードパーティパッケージとしてサポートおよび保守されています。

インストールと設定

pipを使用してパッケージをインストールします。

pip install djangorestframework-oauth

設定と使用方法の詳細については、認証権限に関するDjango REST framework OAuthドキュメントを参照してください。

JSON Web Token Authentication

JSON Web Tokenは比較的新しい標準であり、トークンベースの認証に使用できます。組み込みのTokenAuthenticationスキームとは異なり、JWT認証はトークンを検証するためにデータベースを使用する必要はありません。JWT認証のパッケージはdjangorestframework-simplejwtであり、いくつかの機能とプラグ可能なトークンブラックリストアプリも提供します。

Hawk HTTP Authentication

HawkRESTライブラリはMohawkライブラリを基盤として構築されており、APIでHawk署名付きのリクエストとレスポンスを処理できます。Hawkを使用すると、2つの当事者が共有キーで署名されたメッセージを使用して安全に通信できます。これはHTTP MACアクセス認証OAuth 1.0の一部に基づいていました)に基づいています。

HTTP Signature Authentication

HTTP Signature(現在IETFドラフト)は、HTTPメッセージの送信元認証とメッセージの完全性を達成する方法を提供します。多くのサービスで使用されているAmazonのHTTP署名スキームと同様に、ステートレスなリクエストごとの認証を許可します。Elvio Toccalinoは、使いやすいHTTP署名認証メカニズムを提供するdjangorestframework-httpsignature(古い)パッケージを保守しています。djangorestframework-httpsignatureの更新されたフォークバージョンであるdrf-httpsigを使用できます。

Djoser

Djoserライブラリは、登録、ログイン、ログアウト、パスワードリセット、アカウントアクティブ化などの基本的なアクションを処理するためのビューのセットを提供します。このパッケージはカスタムユーザーモデルと連携し、トークンベースの認証を使用します。これは、Django認証システムのすぐに使用できるREST実装です。

django-rest-auth / dj-rest-auth

このライブラリは、登録、認証(ソーシャルメディア認証を含む)、パスワードリセット、ユーザー詳細の取得と更新などのREST APIエンドポイントのセットを提供します。これらのAPIエンドポイントを使用することで、AngularJS、iOS、Androidなどのクライアントアプリは、ユーザー管理のためにREST APIを介してDjangoバックエンドサイトと独立して通信できます。

現在、このプロジェクトには2つのフォークがあります。

drf-social-oauth2

Drf-social-oauth2は、Facebook、Google、Twitter、Orcidなどの主要なソーシャルoauth2ベンダーで認証するのに役立つフレームワークです。簡単な設定でJWT方式でトークンを生成します。

drfpasswordless

drfpasswordlessは、Django REST FrameworkのTokenAuthenticationスキームに(Medium、Square Cashに触発された)パスワードレスサポートを追加します。ユーザーは、メールアドレスや電話番号などの連絡先に送信されたトークンを使用してログインおよびサインアップします。

django-rest-authemail

django-rest-authemailは、ユーザーのサインアップと認証のためのRESTful APIインターフェースを提供します。ユーザー名ではなく、メールアドレスが認証に使用されます。サインアップ、サインアップメールの検証、ログイン、ログアウト、パスワードリセット、パスワードリセットの検証、メールの変更、メールの変更の検証、パスワードの変更、ユーザーの詳細のためのAPIエンドポイントが用意されています。完全に機能するサンプルプロジェクトと詳細な手順が含まれています。

Django-Rest-Durin

Django-Rest-Durin は、Web/CLI/モバイルAPIクライアントの複数に対して、1つのインターフェースでトークン認証を行う単一のライブラリを構築するという考えに基づいて開発されました。ただし、APIを消費する各APIクライアントごとに異なるトークン設定を許可します。Django-Rest-Frameworkと連携するカスタムモデル、ビュー、パーミッションを介して、ユーザーごとに複数のトークンをサポートします。トークンの有効期限はAPIクライアントごとに異なり、Django管理インターフェースからカスタマイズ可能です。

詳細はドキュメントをご覧ください。