fabien-michel
fabien-michel

Reputation: 2182

How to require django's authentication on strawberry-graphql

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

Answers (1)

capitalg
capitalg

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

Related Questions