knoxとFastAPI-UsersでPython APIのトークンベース認証を強化する
Lukas Schneider
DevOps Engineer · Leapcell

はじめに
現代のウェブ開発において、APIセキュリティは最重要課題です。アプリケーションがますます分散化し、マイクロサービスアーキテクチャが普及するにつれて、堅牢な認証メカニズムはもはや贅沢ではなく、必須となっています。トークンベース認証は、ステートレスな性質、スケーラビリティ、モバイルおよびシングルページアプリケーションへの適合性から、APIの保護のための好ましい方法として登場しました。しかし、単にトークンを使用するだけでは不十分です。トークンのライフサイクルを管理し、その機密性を確保し、一般的な攻撃ベクトルを軽減するには、慎重な検討が必要です。この記事では、Django REST Framework向けのdjango-rest-knox
とFastAPI向けのFastAPI-Users
という2つの強力なフレームワークが、トークン認証のためのセキュリティ機能をどのように強化し、基本的な実装を超えて、より回復力があり開発者に優しいソリューションを提供するのかを掘り下げます。
安全なトークン認証のコアコンセプト
django-rest-knox
とFastAPI-Users
の詳細に入る前に、安全なトークン認証の理解に不可欠な基本的な概念をいくつか明確にしましょう。
トークンベース認証
その核心において、トークンベース認証は、クライアントが認証サーバーに資格情報(ユーザー名とパスワードなど)を送信することを含みます。認証が成功すると、サーバーは暗号化されたトークンを発行します。このトークンは、通常JSON Web Token(JWT)または不透明トークンですが、クライアント側で保存され、保護されたリソースにアクセスするための後続のリクエストごとに送信されます。サーバーは、各リクエストでユーザーを認証するためにトークンを検証します。
不透明トークン vs JWT
- 不透明トークン: これらは、サーバー側の認証セッションへの参照として機能するランダムな文字列です。トークン自体にはユーザー情報は含まれていません。サーバーは、セッションの詳細を取得するためにデータベースでトークンを検索します。これにより、サーバー側での取り消しと再発行が容易になります。
- JWT(JSON Web Tokens): これらのトークンには、ユーザーのクレーム(ユーザーに関する情報)と署名を含むエンコードされたJSONオブジェクトが含まれています。サーバーは、署名キーを持っていれば、毎回データベースをクエリする必要なく、トークンの認証を検証できます。効率的ですが、JWTは長い有効期限が設定されている場合、即時取り消しが困難です。
トークンに関する主要なセキュリティ上の懸念事項
- 総当たり攻撃: トークンや資格情報を推測しようとする度重なる試み。
- リプレイ攻撃: 攻撃者がトークンを傍受し、不正アクセスを得るために再利用すること。
- トークンの乗っ取り/盗難: 攻撃者が有効なトークンを入手すること。これは、クロスサイトスクリプティング(XSS)や中間者攻撃などを通じてしばしば行われます。
- 不適切なトークン取り消し: ユーザーがログアウトした後やセッションが終了した後も有効なままであるトークン。
- 安全でないストレージ: クライアント側で脆弱な場所にトークンが保存されること。
説明するフレームワークは、これらの懸念事項を設計と機能を通じて解決することを目指しています。
django-rest-knox
によるセキュリティの強化
django-rest-knox
は、Django REST Framework(DRF)向けのパッケージであり、安全なトークンベースの認証システムを提供します。単一の永続的なトークンを発行するDRFの組み込みTokenAuthentication
とは異なり、knoxは一度きりの使用、期限切れ、および容易に取り消し可能なトークンに焦点を当てています。主に不透明トークンを使用します。
django-rest-knox
の仕組み
- トークン生成: ユーザーがログインすると、knoxは一意で暗号学的に安全な不透明トークンを生成します。このトークンのハッシュと有効期限をデータベースに保存します。
- クライアント側: プレーンテキストのトークンがクライアントに送信されます。クライアントはこのトークンを(Webアプリの場合は
localStorage
やsessionStorage
、モバイルアプリの場合はセキュアストレージなどに)保存します。 - 認証: 後続のリクエストでは、クライアントはこのトークンを
Authorization
ヘッダーで送信します。 - 検証: サーバーはトークンを受け取り、ハッシュ化して、データベースに保存されているハッシュと比較します。一致が見つかり、トークンが期限切れでない場合、ユーザーは認証されます。
- 一度きりの使用/取り消し: KNOXは、ログアウト時または有効期限切れ時にトークンを取り消すことを許可します。特に、ユーザーごとに複数のトークンを許可することでマルチデバイスログインをサポートし、ユーザーまたは特定のトークンのすべてのトークンを取り消すメカニズムを提供します。
django-rest-knox
の実装例
まず、django-rest-knox
をインストールします。
pip install django-rest-knox
settings.py
でknox
をINSTALLED_APPS
に追加します。
# settings.py INSTALLED_APPS = [ # ...他のアプリ 'rest_framework', 'knox', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'knox.auth.TokenAuthentication', ), # ... }
プロジェクトのurls.py
でKnoxのURLを含めます。
# urls.py from django.contrib import admin from django.urls import path, include from knox import views as knox_views from .views import LoginAPI # カスタムLoginAPIビューがあると仮定 urlpatterns = [ path('admin/', admin.site.urls), path('api/auth/login/', LoginAPI.as_view(), name='knox_login'), path('api/auth/logout/', knox_views.LogoutView.as_view(), name='knox_logout'), path('api/auth/logoutall/', knox_views.LogoutAllView.as_view(), name='knox_logoutall'), # ...その他のAPIエンドポイント ]
ユーザーのログインとトークン生成を処理するカスタムLoginAPI
ビューを作成します。
# your_app/views.py from rest_framework import generics, permissions from rest_framework.response import Response from knox.models import AuthToken from .serializers import UserSerializer, AuthTokenSerializer class LoginAPI(generics.GenericAPIView): serializer_class = AuthTokenSerializer permission_classes = (permissions.AllowAny,) def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.validated_data['user'] _, token = AuthToken.objects.create(user) # トークンを作成し、ユーザーオブジェクトとトークン文字列を返します return Response({