MENU

Django Ninjaチュートリアル16(認証機能)

Django Ninjaチュートリアル16(認証機能)

前回までの記事はこちらです。

前回までの記事が読まれている前提で書いていきますので、まだ読んでいない方はこちらから読み進めて追いついてください。

それでは始めていきましょう。

目次

Django Ninjaの認証機能

Django Ninjaでは、すべてのセキュリティ仕様を調べ、学習する必要なく、標準的な方法で認証と承認を取り扱う、簡単かつ迅速なツールを提供しています。
核となる概念は、APIオペレーションを記述するときに、認証オブジェクトを定義できるということにあります。

django_authを使用した認証機能

from ninja import NinjaAPI
from ninja.security import django_auth

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
)

@app_v7.get("/pets", auth = django_auth)
def pets(request):
  return f"Authenticated user {request.auth}"

この例では、クライアントはDjangoの認証セッション(デフォルトではcookieベースの認証)を使用した場合にのみpetsメソッドを呼び出すことができます。
認証されていない場合は、HTTP 401エラーを返します。

自動生成ドキュメントにはAuthorizeが追加される
Authorizeから入るとcookieベースの認証がデフォルトで用意されている

自動OpenAPIスキーマ

ここでは、クライアントが認証するためにheaderを渡す必要がある場合の例を見ていきます。

Bearer supersecretによる認証機能

from ninja import NinjaAPI
from ninja.security import django_auth, HttpBearer # add

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
)

# add
class AuthBearer(HttpBearer):
  def authenticate(self, request, token):
    if token == "supersecret":
      return token

@app_v7.get("/pets", auth = django_auth)
def pets(request):
  return f"Authenticated user {request.auth}"

# add
@app_v7.get("/bearer", auth = AuthBearer)
def bearer(request):
  return {"token": request.auth}
認証せずに実行すると「Unauthorize」が返される
認証機能はtokenを求められる
設定した「supersecret」を渡すとログインする

グローバル認証

作成したAPIのすべてのメソッドを保護するケースにおいて、auth引数をNinjaAPIコンストラクターから渡すことができます。
また、これらのメソッドの一部を無効にする必要がある場合は、auth引数を渡すことで、操作レベルで再度無効にすることができます。
以下の例では、/token操作の認証が無効になります。

from ninja import NinjaAPI, Form
from ninja.security import django_auth, HttpBearer

class AuthBearer(HttpBearer):
  def authenticate(self, request, token):
    if token == "supersecret":
      return token

# add
class GlobalAuth(HttpBearer):
  def authenticate(self, request, token):
    if token == "supersecret":
      return token

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
  auth = GlobalAuth()
)

〜略〜

# add
@app_v7.post("/token", auth = None)
def get_token(request, username: str = Form(...), password: str = Form(...)):
  if username == "admin" and password =="giraffethinnknslog":
    return {"token": "supersecret"}
生成されたPOSTメソッドは認証マークがついていない

このようにauth = Noneを渡すことで、API毎に認証機能の有効・無効を設定することができます。

利用可能な認証オプション

一部のAPIは、認証にapi_keyを使用します。
api_keyは、クライアントが自分自身を識別するためにAPIの呼び出しを行うときに提供するtokenです。
keyはクエリ文字列で送信できます。

【クエリ文字列で渡す場合】

事前に既定のモデルでClientモデルを作成しておいてください。

from events.models import Client
from ninja import NinjaAPI, Form
from ninja.security import django_auth, HttpBearer, APIKeyQuery # add

〜略〜

# add
class ApiKey(APIKeyQuery):
  param_name = "api_key"
  
  def authenticate(self, request, key):
    try:
      return Client.objects.get(key = key)
    except Client.DoesNotExist:
      pass

api_key = ApiKey()

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
  auth = GlobalAuth()
)

〜略〜

@app_v7.get("/apikey", auth = api_key)
def apikey(request):
  assert isinstance(request.auth, Client)
  return f"Hello {request.auth}"

この例では、GET['api_key']からtokenを取得し、データベースからこのkeyに対応するClientを見つけます。
Clientインスタンスがrequest.auth属性にセットされます。

param_nameはチェックされるGETパラメータの名前です。
これがセットされなければ、デフォルトでkeyが使用されます。

【リクエストヘッダーとして渡す場合】

from ninja import NinjaAPI, Form
from ninja.security import django_auth, HttpBearer, APIKeyQuery, APIKeyHeader

〜略〜
class ApiKeyHeader(APIKeyHeader):
  param_name = "X-API-Key"
  
  def authenticate(self, request, key):
    if key == "supersecret":
      return key

header_key = ApiKeyHeader()

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
  auth = GlobalAuth()
)

〜略〜

@app_v7.get("/headerkey", auth = header_key)
def apikey(request):
  return f"Token = {request.auth}"

まずは認証しない状態でexecuteしてみます。
すると以下のようにUnauthorizeが返され、認証がされていないことが確認できます。

認証されていないため、Unauthorizeが返される

次にAPIの鍵マークから認証してから、executeしてみます。
すると、設定したAPIのとおりの結果が返ってきます。
つまりヘッダーが渡されているということが確認できます。

認証がされ、APIどおりに文字列が返される

【cookieで渡す場合】

from ninja import NinjaAPI, Form
from ninja.security import django_auth, HttpBearer, APIKeyQuery, APIKeyHeader, APIKeyCookie

〜略〜

class CookieKey(APIKeyCookie):
  def authenticate(self, request, key):
    if key == "supersecret":
      return key

cookie_key = CookieKey()

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
  auth = GlobalAuth()
)

〜略〜

@app_v7.get("/cookiekey", auth = cookie_key)
def apikey(request):
  return f"Token = {request.auth}"

HttpBasicAuth

基本的なusernameとpasswordを使用した認証機能も設定できます。

from ninja.security import django_auth, HttpBasicAuth

〜略〜

class BasicAuth(HttpBasicAuth):
  def authenticate(self, request, username, password):
    if username == "admin" and password == "secret":
      return username

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
  csrf = True,
  auth = GlobalAuth()
)

〜略〜

@app_v7.get("/basic", auth = BasicAuth())
def basic(request):
  return {"http_user": request.auth}

上記のように設定するだけで、BasicAuthクラスに設定したusernamepasswordに一致した場合にのみAPIに定義した関数を返します。

今回は、わかりやすいようにusernamepasswordを設定していますが、本来であれば、usernamepasswordをデータベースの一覧で検索することになると思います。
今回はその部分には触れませんが、いずれにせよ、一意の値を設定するケースはほとんどないと思いますのでご注意ください。

ここまで、様々な認証機能と認証手法を見てきました。
Django Ninjaであらかじめ用意されている機能をベースに拡張しながら設定できれば、開発効率も上がりそうです。

複数の認証機能

前述したauth引数を使用する場合、複数の認証機能を設定することもできます。
この場合、auth引数に対し、認証機能として設定した関数をリスト形式で渡します。
例を見てみます。

from ninja.security import APIKeyQuery, APIKeyHeader

class AuthCheck:
  def authenticate(self, request, key):
    if key == "supersecret":
      return key

class QueryKey(AuthCheck, APIKeyQuery):
    pass

class HeaderKey(AuthCheck, APIKeyHeader):
    pass

@api.get("/multiple", auth=[QueryKey(), HeaderKey()])
def multiple(request):
    return f"Token = {request.auth}"

上記の例では、QueryKeyHeaderKey(例示のため、共に内容はpass)という2つの認証機能が用意されている状態で、APIのauth引数にはリスト形式で、両方の関数を渡しています。

この場合、初めにQueryKeyによりAPI Keyによる認証を試みて、それがなければ、header keyによる認証を行います。
どちらもない場合や無効な場合はUnauthorizeを返します。

Router authentication

routerインスタンスを使用する場合においても、auth引数を使用し、宣言されているオペレーションに対して、認証機能を付与することができます。

api.add_router("/events/", events_router, auth=BasicAuth())

また、Routerから作成したrouterインスタンスに対しても、auth引数を使用することができます。

router = Router(auth=BasicAuth())

この場合でも、通常の認証機能の設定と同様に、これらの記述をするよりも前に、BasicAuth関数を定義しておくことが必要です。

例外処理をカスタムする

例外が発生した場合、exception handlerはAPIオペレーションと同じ方法でレスポンスを返します。

作成した例外処理のAPIは自動生成ドキュメントには表示されません。
表示はされませんが、例外処理はこのAPIオペレーションを通じで、内部的に、適切に行われます。

from ninja import NinjaAPI
from ninja.security import HttpBearer

〜略〜

app_v7 = NinjaAPI(
  title = "Django-Ninja Sample App7",
  version = "1.0.7",
)

# 例外処理用の関数を作成(Django Ninjaのデフォルト)
class InvalidToken(Exception):
  pass

class AuthBearer(HttpBearer):
  def authenticate(self, request, token):
    if token == "supersecret":
      return token
    raise InvalidToken # tokenと一致しない場合に例外を発生

# Django Ninjaでは例外をAPIと同じ方法で定義することができる
@app_v7.exception_handler(InvalidToken) # InvalideToken関数を引数に取る
# レスポンスを生成
def on_invalid_token(request, exc):
  return app_v7.create_response(
    request,
    {"detail": "Invalid token supplied."},
    status = 401
  )
自動生成ドキュメントには例外処理のAPIは出てこない
適切なTokenではない場合、例外処理APIで規定した値が返ってくる
右上の認証マークから正しく認証すると例外は生じない

最後に

今回はアプリケーションでは必須となる認証機能を見てきました。
Django Ninjaではデフォルトで様々な認証機能を備えていますので、これを活用し、素早く認証機能を作成することが可能です。

今回はドキュメントに従って進めたため、より個別の具体的な認証方法までは設定しませんでしたが、基本的な認証手順については確認できたのではないかと思います。

次回は、エラー処理等についてみていきたいと思います。

Django Ninjaに興味がある方は見てみてください。

今回はここまでです。
また次回もよろしくお願いします。

Django Ninjaチュートリアル16(認証機能)

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、ふじです。
Python、Django、FastAPI、React.js、Next.jsを学習している、ずっと文系のプログラミング独学者です。

目次