I have a DRF API with protected endpoints that returns filtered data depending on what a user has permission to access.
I have a separate Django OAuth2 provider which contains the user models and the values necessary to determine what the user has permission to access.
The user should be able to authenticate via a login endpoint on the DRF API. The API in turn gets a token from the Oauth2 provider on behalf of the user, and makes a few calls to get a list of resources the user is allowed to access.
Ideally the DRF API would then generate a token and return it to the user. Whenever the user makes a subsequent request (after login) using the token, the API would be able to filter results via the values returned by calls to the Oauth provider.
The question is how to store this information. This feels similar to storing data in an anonymous user session, but using a request header instead of a cookie. I've considered rolling a customized version of django.contrib.sessions.middleware.SessionMiddleware, but I'd prefer to use an established method instead of writing custom code, as this seems like it should not be a unique problem.
To reiterate: Is it possible to create an anonymous user session, store information it it, and retrieve the session via a request header instead of a cookie?
Here is the original SessionMiddleware.process_request provided by Django. Lets take a quick look at it.
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = self.SessionStore(session_key)
We can clearly see that it explicitly gets the session identifier form the cookies using the SESSION_COOKIE_NAME property defined in the settings. Hence, we absolutely must create our own subclass of this SessionMiddleware and define our own process_request behaviour.
Irrespective of whether the incoming token is authenticated or not, we need to retrieve the token value from the header, and use that to initiate our session engine. Here's how it might look:
from django.contrib.sessions.middleware import SessionMiddleware
from django.conf import settings
class CustomSessionMiddleware(SessionMiddleware):
def process_request(self, request):
session_key = request.META.get("HTTP_%s" % settings.SESSION_KEY_NAME, None)
request.session = self.SessionStore(session_key)
Make sure you set the SESSION_KEY_NAME property in your django settings file to the name of the header key in which this token will be sent. Then, replace django's original SessionMiddleware with the path to your custom session middleware and your requests.session should start giving you data based on the input token.
Note: You may also need to modify the process_response behaviour since you may not need to send back Set-Cookie headers.
Related
I'm trying to create a kind of quick response form where a user does not need to login but is identified with his uuid (in the url). Moreover i need to restrict his permissions for this session so that he only could to a few things.
User gets url with UUID and ID of event (Where he can response)
User clicks on link and response page opens
User is identified via uuid
With the id of the event the event response form is generated
User can chose options and submit the form
Normally there are multiple other options (e.g. in the menue) available but these should only be accessible if user identifies with his username and password.
Of course i could write a special view but is there a more elegant way where i could reuse my existing view for the response?
First you'll want to use the UUID to trigger the login.
How you approach this will depend on whether you mind using a specific login url/view or you want people to arrive directly at the intended url/view (just with the UUID parameter). If you use a specific view you'll check the UUID, (presumably mark it as used/expired), then login the user. Alternatively for the UUID to checked and have users login via any url/view, you'll want a to use a custom middleware that's placed ahead of AuthenticationMiddleware in your middleware settings. In each case, once you have the logged in the user, you'll want to store in session some sort of flag to indicate they've auth'd via a temporary login UUID.
Now that you've authenticated and logged in the user via the UUID, it's time to serve them the view. At this point in your View you'll check for presence of the flag in the user session to determine their permissions.
You could go further and create another middleware class to encapsulate the session lookups and add a property, say "is_auth_temporary", to the request.User object that is a sibling to the is_anonymous and is_authenticated property.
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.
I have a simple REST API that needs to be accessible both from a web app as well as from a remote service.
The remote service is authenticated via a custom HTTP header containing an API key.
How can I protect that API such that requests from a web browser are CSRF protected, but the CSRF check is not done when authenticated via API key? Or, in general, how can I enable CSRF protection for some requests on a specific view, but not others?
Currently, I have a decorator that checks the request for an API key and authenticates the API user roughly this way:
# Regular auth
if request.user.is_authenticated():
# DO CSRF verification, then continue calling the view
elif 'HTTP_X_API_KEY' in request.META:
api_key = request.META['HTTP_X_API_KEY']
user = authenticate(username=settings.API_USER_NAME, password=api_key)
login(request, user)
# If user is authenticated and autzorized, continue calling the view
# but WITHOUT invoking CSRF protection
The problem as stated is, that I only want CSRF protection for regular users, but not for the API user.
Okay, so after a bit more of tinkering around, the solution was to disable the CSRF middleware and enable csrf_protect for all cases where CSRF protection is required.
This works in the special case of the API, as every call is being decorated anyway, so the risk of some view being forgotten is negligible.
What does not work however, is the other way around, using csrf_exempt. The problem there is that the decorator only sets a csrf_exempt property on the view, which, if you have multiple decorators, may be masked again, even if you use functools.wraps.
Also, since csrf_exempt puts a property on the view, it cannot be dynamically enabled or disabled based on the request's content - it's really a very static thing to do.
Are there basic authentication examples with Django and Tastypie?. I'm a little bit confused about how the authentication in Django works, specially with Tastypie.I wanna know how the authentication works with api keys and how to authenticate a user with the built-in User model which Django has. Any suggestion or code are really appreciated.
Thanks.
Just to answer your questions regarding authentication:
How the authentication in Django works?
Django authentication required SessionMiddleware to work. Once a session has been loaded, the Django authentication backend reads a special cookie _auth_user (IIRC) which contains currently logged in user's ID. If you have access to the django shell, you can manipulate it and make yourself logged in as any user! Once the backend notices there is a _auth_user key, it then adds a lazy User object to the request (so it delays the User.objects.get(...) until it is really needed). If there is no such key in the session dict, the user is claimed to be anonymous and an instance of AnonymousUser is added to the request object instead.
How does the authentication work in Tastypie?
Before your resource view is executed, a Resource.is_authenticated(request) method is called, which in turn calls the is_authenticated(request) method of the authentication backend of your the Resource of your choice. If the method returns False, the authentication is claimed to be failed and returns with Unauthorized error. If the method returns a HttpResponse, the response is returned instead. If the method returns True, the request is claimed to have been authenticated.
How does User model authentication work in Tastypie?
The User model authentication can be performed using SessionAuthentication backend provided by the Tastypie itself. What it does is creating a session for the current request so that the authentication middleware can then automatically insert relevant user model to the request. Notice that for this method to work, your API client has to support storing cookies and resending them in future requests.
You might find this useful. It allows you to authenticate the user based on the Django session cookie.
https://github.com/amezcua/TastyPie-DjangoCookie-Auth/blob/master/DjangoCookieAuth.py
I am using this in my application and it works!
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.