はじめに#

この記事ではstrawberry x djangoにおいてのユーザー認証の方法についてを解説します。

通常は strawberryではpermissionを使ってユーザー認証をしたり、もしくはStrawberryとは関係ありませんがdjangoのmiddleware を使って認証処理をする人がいるかもしれません。(実際に弊社のマジックリンクの実現にはTokenログインするためにmiddlewareを書いています。)

さて、今回はまずはstrawberryの公式に乗っているpermissionを用いて認証する方法を紹介したあと、その問題点を説明してextensionを使った方式でユーザー認証する方法を紹介します。

ユーザー認証を使う(permission編)#

DjangoのUser認証をつかってログインしていないユーザだけに graphql を使いたい場合は、permission_classes を使います。


from strawberry.permission import BasePermission


class IsAuthenticated(BasePermission):
    message = "User is not authenticated"

    # This method can also be async!
    def has_permission(
        self, source: typing.Any, info: strawberry.Info, **kwargs
    ) -> bool:
        return False

@strawberry.type
class Mutation:
    @strawberry.mutation(permission_classes=[IsAuthenticated])
    def update_user_age(self, info: Info[StrawberryDjangoContext, None], age: int) -> UserType:
        user = cast(User, info.context.request.user)
        ...

isAuthenticatedはこちらの公式の書き方を参考にしています。

permissionを使ったやり方の問題は、認証できなかった場合例外が発出することです。これは、例えばエラー監視のsentryなどで監視している場合、API の不正利用を試みるユーザーに対して、認証エラーが頻繁に発生し、sentry の通知が大量に送信されてしまうのと、そもそも例外はすこしどうなの?って思うところがあります。

この問題を解決するためにextensions を使います。

ユーザー認証(extensions編)#

早速ですが、extensionをつかって例外を出さずにauthenticationErrorを返す方法について説明します。



from typing import Any, Callable, List

from strawberry.django.context import StrawberryDjangoContext
from strawberry.extensions import FieldExtension
from strawberry.types import Info

@strawberry.type
class AuthenticationError:
    auth_error_message: str


class AuthExtension(FieldExtension):
    def resolve(
        self, next_: Callable[..., Any], source: Any, info: Info[StrawberryDjangoContext, None], **kwargs
    ) -> AuthenticationError | Any:

        if not self.is_authenticated(info):
            return AuthenticationError(auth_error_message="User is not authenticated")

        else:
            return next_(source, info, **kwargs)

    def is_authenticated(self, info: Info[StrawberryDjangoContext, None]) -> bool:
        # 認証ロジックをここに記述
        # ユーザーの権限を確認したり、API キーを検証したりする処理 etc ...
        return info.context.request.user.is_authenticated

@strawberry.type
class Mutation:
    @strawberry.mutation(extensions=[AuthExtension()])
    def update_user_age(self, info: Info[StrawberryDjangoContext, None], age: int) -> UserType | AuthenticationError:
        ....

こうすると例外処理をせず、AuthenticationError を返すという形で実装ができます。FieldExtensionの説明はぜひ公式をご参照ください。

カスタムエラーハンドリングによって、クライアントに分かりやすいエラーメッセージを返せることもまた一つ大きな特徴かなと感じています。

まとめ#

今回は認証法についてまとめた記事を書きました。実はサラッと書きましたが、extensionに行き着くまでにかなり四苦八苦しました。iamfax.comの場合はsetnryというエラー監視を使っており、例外が起きるたびに通知が来ることはいい気持ちがしませんでした。また、なにかAuthエラーのときは例外が発生するというのは奇妙な感じもします。

このブログを読んだ方で、Extension以外を使うやり方をしっているかた、もしくはpermissionをつかっても例外発出せずつかう方法を存じ上げてる方がおりましたら、是非コメント欄にコメントをいただけますと幸いです。

この方法以外にも、より良い認証方法をご存知でしたら、コメント欄で教えてください!

戻る