GraphQL を用いたアプリケーションでは、認証(Authentication)と認可(Authorization)の概念が重要です。認証はユーザーがシステムにログインしているか、または「認識されているか」を判断し、認可は認証されたユーザーが特定のリソースにアクセスできるかどうかを確認します。この二つは混同されることが多いですが、実際には異なるプロセスです。GraphQL での認証と認可の実装には少し工夫が必要ですが、適切に行うことでセキュアなアプリケーションを構築できます。
本記事では、GraphQL を用いた認証と認可の実装方法について解説します。
認証と認可の概要
認証(Authentication):
ユーザーがシステムにログインしているか、正当なユーザーであるかを確認します。GraphQL では、通常、ユーザーがリクエストを送信する際にトークン(JWTなど)を使って認証を行います。
認可(Authorization):
認証されたユーザーが、特定のリソースやアクションにアクセスできるかどうかを判断します。たとえば、あるユーザーが特定のレストランの予約をキャンセルする権限を持っているかどうかを確認します。
これらのプロセスは、GraphQL の単一のエンドポイント(例:/graphql)で行われるため、複雑に見えるかもしれませんが、適切な方法で実装することが可能です。
アプリケーション設定
まず、ユーザーのサインアップ(登録)とサインイン(ログイン)を実装します。以下に示すのは、基本的なユーザーモデルです。
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from db import Base
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, autoincrement=True)
email = Column(String, unique=True)
password = Column(String)
table_bookings = relationship("TableBooking")
サインアップ(Sign Up)
ユーザーがメールアドレスとパスワードを使ってサインアップする処理を行います。
# api/graphql.py
import graphene
from service import sign_up
class SignUp(graphene.Mutation):
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
user = graphene.Field(UserNode)
def mutate(self, info, email: str, password: str):
session = info.context["session"]
user = sign_up(session, email, password)
return SignUp(user=user)
sign_up 関数は新しいユーザーをデータベースに追加します。次に、サインインの処理に進みます。
サインイン(Sign In)
ユーザーがサインインすると、JWT(JSON Web Token)が発行され、以後のリクエストにおいて認証が行われます。
# api/graphql.py
import graphene
from service import sign_in
class SignIn(graphene.Mutation):
class Arguments:
email = graphene.String(required=True)
password = graphene.String(required=True)
token = graphene.String()
def mutate(self, info, email: str, password: str):
session = info.context["session"]
token = sign_in(session, email, password)
return SignIn(token=token)
sign_in 関数はユーザーの資格情報を確認し、JWT トークンを返します。
認証の実装
認証は、ユーザーが送信するリクエストの「Authorization」ヘッダーに含まれるトークンを使って行います。以下の sign_in_required デコレーターを使用して、GraphQL のフィールドリゾルバーで認証を行います。
# api/auth.py
from functools import wraps
from auth import get_user_by_token
from models import User
class UnauthenticatedUser(Exception):
pass
def sign_in_required():
def decorator(func):
@wraps(func)
def wrapper(root, info, *args, **kwargs):
kwargs["current_user"] = get_current_user(info.context)
return func(root, info, *args, **kwargs)
return wrapper
return decorator
def get_current_user(context) -> User:
try:
token = get_token_from_request(context["request"])
user = get_user_by_token(context["session"], token)
if not user:
raise UnauthenticatedUser("UnauthenticatedUser")
return user
except KeyError:
raise UnauthenticatedUser("UnauthenticatedUser")
def get_token_from_request(request) -> str:
header = request.headers["Authorization"]
token = header.replace("Bearer ", "", 1)
return token
このデコレーターを用いることで、認証されていないユーザーは保護されたフィールドにアクセスできなくなります。
認可の実装
認可は、認証されたユーザーが特定のアクションを実行する権限を持っているかどうかを確認します。例えば、ユーザーが自分のレストラン予約をキャンセルすることは許可されますが、他のユーザーの予約をキャンセルすることは許可されません。
# api/auth.py
import re
from functools import wraps
from graphene.relay.node import from_global_id
from auth import authorize
from models import User
def camel_to_snake(name: str) -> str:
"""CamelCase -> camel_case"""
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
class UnauthorizedAccess(Exception):
pass
def authorize_required(model):
def decorator(func):
@wraps(func)
def wrapper(root, info, *args, **kwargs):
kwargs["current_user"] = get_current_user(info.context)
model_name = model.__name__
gid_field_name = f"{camel_to_snake(model_name)}_gid"
instance_gid = kwargs[gid_field_name]
instance_model_name, instance_id = from_global_id(instance_gid)
if instance_model_name != f"{model_name}Node":
raise UnauthorizedAccess("UnauthorizedAccess")
instance = info.context["session"].query(model).get(instance_id)
if not instance:
raise UnauthorizedAccess("UnauthorizedAccess")
kwargs["instance"] = instance
if not authorize(instance, kwargs["current_user"]):
raise UnauthorizedAccess("UnauthorizedAccess")
return func(root, info, *args, **kwargs)
return wrapper
return decorator
この authorize_required デコレーターを使って、ユーザーが権限を持っている場合のみ、特定のアクションを実行できるようにします。
結論
GraphQL での認証と認可は、単一のエンドポイントで複雑に見えますが、適切に実装すれば非常に柔軟で強力なシステムを作成できます。本記事では、基本的なサインアップ、サインイン、認証、認可の仕組みを Python で実装する方法を紹介しました。この実装は、どんな GraphQL プロジェクトにも応用可能です。