Reputation: 2182
I want to require django's user authentication in order to access the graphql
view.
My first attempt was to decorate the view with login_required
:
from django.contrib.auth.decorators import login_required
from django.urls import path
urlpatterns = [
path("graphql/sync", login_required(GraphQLView.as_view(schema=schema))),
path("graphql", login_required(AsyncGraphQLView.as_view(schema=schema))),
]
It work for the sync view but not for the async one since login_required does not async aware.
For now I endend-up to write my own view which copy the login_required behavior:
from urllib.parse import urlparse
from asgiref.sync import sync_to_async
from strawberry.django.views import AsyncGraphQLView
from django.conf import settings
from django.contrib.auth.views import redirect_to_login
from django.shortcuts import resolve_url
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
class LoginRequiredAsyncGraphQLView(AsyncGraphQLView):
@method_decorator(csrf_exempt)
async def dispatch(self, request, *args, **kwargs):
is_authenticated = await sync_to_async(lambda: request.user.is_authenticated)()
if not is_authenticated:
path = request.build_absolute_uri()
resolved_login_url = resolve_url(settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if (not login_scheme or login_scheme == current_scheme) and (
not login_netloc or login_netloc == current_netloc
):
path = request.get_full_path()
return redirect_to_login(path, resolved_login_url)
return await super().dispatch(request, *args, **kwargs)
# urls.py
urlpatterns = [
path("graphql", LoginRequiredAsyncGraphQLView.as_view(schema=schema)),
]
But it seems to not be a nice solution and I bet there is something cleaner.
There is a better solution to have a login_required
decorator compatible with async view ?
Upvotes: 4
Views: 1764
Reputation: 663
This is what I have come up with, which allows to determine the access on a per-field basis.
import strawberry
import strawberry.django
from django.http.request import HttpRequest
from django.core.exceptions import PermissionDenied
from strawberry_django.fields.field import StrawberryDjangoField
class AuthStrawberryDjangoField(StrawberryDjangoField):
def resolver(self, info, source, **kwargs):
request: HttpRequest = info.context.request
if not request.user.is_authenticated:
raise PermissionDenied()
return super().resolver(info, source, **kwargs)
@strawberry.type
class Query:
foo: List[Foo] = AuthStrawberryDjangoField()
For a mutation you can use
async def graphql_check_authenticated(info: Info):
auth = await sync_to_async(lambda: info.context.request.user.is_authenticated)()
if auth is False:
raise PermissionDenied()
@strawberry.type
class Mutation:
@strawberry.mutation
async def add_foo(self, info: Info, new_foo: FooInput) -> None:
await graphql_check_authenticated(info)
# ...
See https://stackoverflow.com/a/72796313/3475778 why this is not a decorator.
I think this is not pure Graphql as this will return an error instead of a typed error message, maybe I will update it in the future on how to properly implement it.
Upvotes: 3