How to restrict anonymous users from GraphQL API endpoint? - django

Django has two approaches.
Regular DRF restricts user on Middleware level. So not logged in user doesn't reach anything.
GraphQL, on contrary, uses "per method" approach. So middleware passes all the request and each method. But afterward method calls decorator.
I want to implement 1st approach but for GraphQL. But in that case I need to open path for login mutation. How can I extract mutation name from payload?

If you want restrict a GraphQL API endpoint to Django logged in users, you can do it by extending GraphQLView with LoginRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from graphene_django.views import GraphQLView
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
"""Adds a login requirement to graphQL API access via main endpoint."""
pass
and then adding this view to your urls.py like
path('api/', PrivateGraphQLView.as_view(schema=schema), name='api')
in the usual way as per the docs.
If you don't want to protect your entire API, you can create another schema and endpoint for the unprotected queries and mutations, which allows a clear separation between each. For example in urls.py:
path('public_api/', GraphQLView.as_view(schema=public_schema), name='public_api')
Note that every API endpoint must have at least one query to work or it will cause an assertion error.

Not sure if it serves your purpose, but I've used the following library which used JWT authentication with graphene similar to how JWT with DRF works!
https://github.com/flavors/django-graphql-jwt

Related

Flask Middleware to be Express-js like

I wonder if it's possible to attach a middleware/function for a specific route like express js. http://expressjs.com/en/guide/using-middleware.html#middleware.application
In my understanding, it seems that flask(WSGI) middleware applies to all request.
The equivalent pattern is a route decorator. Flask has some docs about it. It is pretty flexible, you can validate anything in HTTP request and change the response based on that.
For example, Flask-Login has the #login_required decorator to add a user authentication step before the route is called.

Why does Django REST Framework provide different Authentication mechanisms

Why does Django REST Framework implement a different Authentication mechanism than the built-in Django mechanism?
To wit, there are two settings classes that one can configure:
settings.AUTHENTICATION_BACKENDS which handles the Django-level authentication, and
settings.REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] which authenticates at the REST-Framework level
The problem I'm experiencing is that I have a Middleware layer which checks whether a user is logged-in or not.
When using a web client which authenticates via sessions, this works fine. However, from mobile or when running the test suite (i.e. authenticating using HTTP headers and tokens), the middleware detects the user as an AnonymousUser, but by the time we get to the REST Framework layer, the HTTP Authorization header is read, and the user is logged-in.
Why do these not both happen BEFORE the middleware? Furthermore, why doesn't REST Framework's authentication methods not rely on the Django authentication backend?
Django Rest Framework does not perform authentication in middleware by default for the same reason that Django does not perform authentication in middleware by default: middleware applies to ALL views, and is overkill when you only want to authenticate access to a small portion of your views. Also, having the ability to provide different authentication methods for different API endpoints is a very handy feature.
Rest Framework's authentication methods do not rely on the Django authentication backend because the Django's backend is optimised for the common case, and is intimitely linked to the user model. Rest Framework aims to make it easy to:
Use many different authentication methods. (You want HMAC based authentication? done! This is not possible with django auth framework)
Serve API data without ever needing a database behind it. (You have a redis database with all your data in-memory? Serve it in milliseconds without ever waiting for a round trip to DB user model.)
Thomas' answer explains the why quite well. My answer deals more with what you can do about it. I also have a middleware class that determines the current user. I need this because we have some automatic processes (cron) and I want to attribute these to a super user, and I have some model functions that don't have access to the request object but need to know who the current user is for various functions. So this created some issues as you noticed when we added an API via the REST framework. Here's my solution (yours will need to be adjusted to your use case):
from threading import local
_user = local() # thread-safe storage for current user used by middleware below
class CurrentUserMiddleware(object):
""" Defines Middleware for storing the currently active user """
def process_request(self, request):
if not request:
# Clear the current user if also clearing the request.
_user.value = 1 # One represents automatic actions done by super admin
else:
_user.value = request.user
if not _user.value or not _user.value.is_authenticated:
try:
authenticator = TokenAuthentication()
auth = authenticator.authenticate(request)
if auth:
_user.value = auth[0]
except Exception as e:
pass
def process_response(self, request, response):
_user.value = None
return response
The authenticator bit deals with seeing if the request has been authenticated by the REST Framework and getting the user.

modpython django basic auth handler does not pass user to view

I'm using django with apache mod_python. I authenticate users via the basic auth handler (django.contrib.auth.handlers.modpython) [1]. My views get only an instance of AnonymousUser passed in request.user. What am I doing wrong?
[1]: it's an API that is https only, so it shouldn't be a security problem.
I figured it out by myself now: In case you need to do something user specific in a view that is exposed via basic authentication don't let apache handle authentication for you.
If you want HTTP Basic Authentication for all your views write (or get) a Middleware. If, like me, you want to expose only a few views via basic auth create a decorator.
http://djangosnippets.org/snippets/243/ is a good start.

Django: Applying mutilple access control decorators to a view

I'm attempting to expose a single API call using three different authentication mechanisms: django's login_required , HTTP basic auth, and OAuth. I have decorators for all three but can't quite figure out how to have them all get along smoothly.
The required logic is to allow access to the view if any of the decorators / authentication mechanisms are valid for the user's request - basically an OR. However, if I simply include all three decorators then they all want to be satisfied before letting the request through - an AND.
What's a good way to deal with this?
I'm not sure you can. Suppose the user isn't logged in: if using login_required the server would redirect to a login form, whereas using basic auth, the server would return a 401 error page with a WWW-Authenticate response header. Which of these do you want to happen? I don't see how it could be both.

Django Middleware + URL's

Can a middleware check to see if a value is in the url, such as an image id ("/image/152/"), and if it is then do some checks to make sure the current user has permission to view that image and if not redirect to another url?
I had to roll my own permissions for this site I am working on and I don't want to clog up almost every view I write for the whole site with the same code, so I thought a middleware would be a good idea for this, but I'm not sure how to go about doing it.
Yes, this is possible. The django middleware docs for process_request indicate that:
def process_request(self, request)
request is an HttpRequest object. This method is called on each request, before Django decides which view to execute.
process_request() should return either None or an HttpResponse object. If it returns None, Django will continue processing this request, executing any other middleware and, then, the appropriate view. If it returns an HttpResponse object, Django won't bother calling ANY other request, view or exception middleware, or the appropriate view; it'll return that HttpResponse.
The HttpRequest object has a path attribute that will give you the URL that was requested.
If you prefer, however, note that you can also extend Django's system for authentication backends to populate the user in the request with permissions based on any arbitrary criteria, such as perhaps your hand-rolled permissions scheme. This way, you can leverage the default authentication decorators (#permission_required and #user_passes_test), and other apps/the admin site will be able to honour your permissions as well. The User object and permissions created do not need to reside in Django's user/permission tables, and can be created virtually on login; I've had a fair amount of success with this.
See Writing an authentication backend if this appeals.
If you implement authorization (Permission system) in middleware, you will end up with two options:
Check URL and allow to access
Check URL and reject access
If your requirement is that much simple, it is fine, since you don't have to touch views.
But in general, Permission system is much complex than that, for example:
User can access FOO/show_all/
But, he can't see or access foo instance, i.e, FOO/show/foo_1/
Since, he can't access foo_1 instance, we should not show them in show_all
(all foo instances)
If you want implement above 3 together, I suggest you write your own custom authorization backend for Django. All you need to do is implement few methods (your specific logic) and attach as backend.