We are currently developing a mobile application(using ionic) for our site that was built using Django. We used django sessions on the site everytime a user logs in. From what I understand, django session sets the session id in the client that is stored in the cookie of the browser. How can we set this session id in the mobile app, if the mobile app is separate from django?
I see at least three ways you can approach this:
find a way to get your ionic app to work with django's cookies (session and CSRF).
change your django app in three ways: 1) have login/logout views that give an authentication token to the mobile application, 2) extend SessionMiddleware to extract the session id from the authentication token sent via the "Authorization" HTTP header of the mobile app's requests, 3) extend django's CSRF middleware to make requests that do not come from web browsers exempt from CSRF checks.
try to use existing solutions for adding JWT (Json Web Token) authentication to Django.
Getting WebView-based hybrid mobile apps to work properly with cookies on both android and ios seems to be non-trivial (e.g. Handling cookies in PhoneGap/Cordova).
The existing solutions for JWT-auth I found thus far don't seem to use Django's sessions (which I find rather convenient for the sake of having a uniform solution for killing the sessions of users who get their phones/computers stolen or their accounts hacked). If someone knows of a pluggable solution that does token-authentication on top of django's regular sessions, please comment.
To elaborate on method 2:
When your mobile app doesn't handle cookies, it can't even log in due to Django's CSRF check. That's why requests coming from your mobile app need to be exempt from CSRF protection.
Note: Do NOT simply disable CSRF protection. While the mobile application isn't vulnerable to CSRF attacks, the browsers of people who are visiting the existing website still are.
To extend the CSRF mechanism to exempt non-browser requests, you need an effective way to determine whether a request is coming from a web browser. There's a bunch of HTTP headers (E.g. Referer, Cookie, X-Requested-With, Origin) we can look at to get an idea. Let's assume for now that your legitimate website users don't spoof their headers.
Use the same "is-web-browser"-check you used for CSRF-exemption to protect the mobile app's login/logout views.
Example Code (for an Android mobile app, iOS headers coming from the mobile app are likely different):
def is_mobile_app_access(request):
return request.META.get('HTTP_REFERER', None) is None
and request.META.get('HTTP_COOKIE', None) is None
and request.META.get('HTTP_X_REQUESTED_WITH', None) == 'your.app.name.here'
and request.META.get('HTTP_ORIGIN', None) == 'file://'
class CustomCsrfViewMiddleware(CsrfViewMiddleware):
def process_view(self, request, callback, callback_args, callback_kwargs):
if is_mobile_app_access(request):
return None
else:
return super(CustomCsrfViewMiddleware, self).process_view(request, callback, callback_args, callback_kwargs)
def process_response(self, request, response):
if is_mobile_app_access(request):
return response
else:
return super(CustomCsrfViewMiddleware, self).process_response(request, response)
class CustomSessionMiddleware(SessionMiddleware):
def process_request(self, request):
if is_mobile_app_access(request):
session_key = request.META.get("HTTP_AUTHORIZATION", None)
else:
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
If anyone sees something fundamentally wrong with this approach, please let me know. I suppose the weakest link in this approach is how reliable the is_mobile_app_access check is.
What did you end up doing?
Related
I have a little shop-like app powered by Django (1.7), that needs to process credit cards using external gateway.
I have my own User table for customers. They login via form and auth data is kept within sessions.
Now, payment gateway needs to access my app's predefined urls via SSL & Basic Auth.
How I can protect this (and only this) url with HTTP Basic Auth? Also I don't want to add the gateway user in my User table. Also, I don't want my custommers access that secret URL with their credentials (of course, the URL is not displayed to users, but you should never trust the users, right?)
I could to that with HTTP-server (nginx/apache) settings, but then, I can't test this behavior via django test framework.
Is there a solution for this?
You can manually check the request.META['REMOTE_USER'] and request.META['HTTP_AUTHORIZATION'] against your custom credentials, wherever those credentials reside. If the request isn't properly authorized, you need to send back a HTTP 401 Unauthorized response:
def some_view(request, **kwargs):
if not remote_user_is_authenticated(...):
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
If you're using Apache with mod_wsgi, the REMOTE_USER Meta header won't be passed on to your application by default; you'll need to put WSGIPassAuthorization On in your Apache config file. Nginx probably has a similar setting (or not, I really don't know :P).
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.
Does session authentication in django have anaything to do with cookies?Would it work if a user has cookies disabled on his browser? Should django warn users if their browsers have cookies disabled?
No, authentication is cookie-based - session ID stored in cookies!
The Django sessions framework is entirely, and solely, cookie-based.
It does not fall back to putting session IDs in URLs as a last resort,
as PHP does. This is an intentional design decision. Not only does
that behavior make URLs ugly, it makes your site vulnerable to
session-ID theft via the “Referer” header.
There is workarounds, for example you can put the session ID in the query string. Check this article: http://www.stereoplex.com/blog/cookieless-django-sessions-and-authentication-with
Warning from author: don't do what I'm about to describe unless you understand the potential security consequences
Middleware that get session id from request.GET and put it in request.COOKIES (FakeSessionCookie middleware has to be placed before the SessionMiddleware in settings.py):
from django.conf import settings
class FakeSessionCookieMiddleware(object):
def process_request(self, request):
if not request.COOKIES.has_key(settings.SESSION_COOKIE_NAME) \
and request.GET.has_key(settings.SESSION_COOKIE_NAME):
request.COOKIES[settings.SESSION_COOKIE_NAME] = \
request.GET[settings.SESSION_COOKIE_NAME]
After authentication you should include session id as url (GET) parameter in all requests to server.
According to docs:
Django provides full support for anonymous sessions. The session framework lets you store and retrieve arbitrary data on a per-site-visitor basis. It stores data on the server side and abstracts the sending and receiving of cookies. Cookies contain a session ID – not the data itself...
more here
django uses cookie based sessions, so without cookies authentication won't work.
Most of my GET requests are fired by Angular which makes it easy to set the CSRF Token in the header. This already works out of the box for POST requests, however I was wondering whether I could use CSRF protection for GET requests as well.
My initial instinct was to add #csrf_protect before the View's get method, for instance:
class ProjectView(View):
#csrf_protect
def get(self, request, *args, **kwargs):
...
However this produces the error:
AttributeError at /project
'ProjectView' object has no attribute 'COOKIES'
File "c:\Apps\msysgit\simpletask\lib\site-packages\django\middleware\csrf.py" in process_view
95. request.COOKIES[settings.CSRF_COOKIE_NAME])
So I'm guessing there's something which a POST request provides which a GET request does not. Is there a better way to do this?
Also, before anybody mentions that all sensitive information should be pulled via POST requests (as I've seen a few times lately), my reason for trying to stick with GET is I'm trying to build according to RESTful guidelines, meaning GET requests are for pulling data, POST is for new records, PUT is for updates and DELETE is for data deletion.
EDIT:
Since there seems to be a sentiment that I shouldn't be attempting the above, while I do now agree somewhat, the exploit(s) I'm attempting to prevent are discussed in these 3 links:
http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
http://haacked.com/archive/2009/06/24/json-hijacking.aspx
http://balpha.de/2013/02/plain-text-considered-harmful-a-cross-domain-exploit/
I don't think you quite understand what CSRF actually protects. CSRF exploits the trust that a site has in a user's browser; by exploiting a session cookie that allows the attacker to run commands on behalf of the user.
The site trusts the request because it hasn't expired the session cookie for the browser. This is why in most CSRF mitigation techniques a unique OTP for each request that has a side effect is generated. The attacker would not know the key generated for the OTP, and hence cannot forge a legitimate request.
For all URLs that have a side effect (like POST, PUT, DELETE) you would need CSRF. For GET it doesn't make any sense to have it, since GET requests - if you are doing REST correctly - should be idempotent - if run multiple times, they return the same results and they should not have any side effects.
CSRF protection does not prevent information disclosure or man in the middle attacks. So if you have sensitive information you want to protect, use HTTPS. To prevent unregulated use of your API, use authentication and authorization.
You need to use method_decorator
https://docs.djangoproject.com/en/1.4/topics/class-based-views/#decorating-the-class
So your code will be
class ProjectView(View):`
#method_decorator(csrf_protect)
def get(self, request, *args, **kwargs):
[EDIT]
However csrf_protect will check only POST requests (it has `if request.method == 'POST' check) so you have to implement something by your own.
I'm developing a solution for my company with the following architecture: a RESTfull Web Service built on django which provide authentication and persistence layer to both a web client app and a mobile client app (which is written using phonegap).
We have been looking a lot all over the internet about authentication methods on client side, providing support for both web and mobile client app, and from what we have found (which is very poor) we are thinking about generating an API key for each user logged in from a mobile client app, and saving this API key in the local storage of the device; and, in the web client, using the traditional cookie session management, including a CSRF token in POST, PUT, and DELETE requests.
We'd like to know what are the best practices on the authentication methods and, is this approach good enough? Are there any other methods to deal with authentication? which one is the best one?
We are trying to void using oAuth, since it add complexity to our development.
We have already checked this question but its answers have not been of much help to us, since we are using phonegap, and have the mentioned architecture.
Thanks for your help!
If you really really really want to create own solution. It's my old bad solution before oAuth times.
Create view which return some key after successful login with username/pass and add generated access_key to db
Check key in request => if exist in db => login
#pseudo code
#view
from django.contrib.auth import authenticate, login
def get_my_token(request, username, password):
user = authenticate(username, password)
if user is not None:
login(request,user)
#first should check has access_key
try:
return UserAuth.objects.filter(user=user).access_key
except:
pass
access_key = 'somecrazy_random_unique_number'
user_auth = UserAuth()
user_auth.user = user
user_auth.access_key = access_key
user_auth.save()
return access_key
Now you can save access_key somewhere and add as header 'access_key_or_any_other_name' to every call to rest resources. Create authentication middleware, not auth backend.
#auth_middelware
class StupidNoAuthMid(object):
def process_request(self, request):
access_key = reuest.META['access_key_or_any_other_name']:
try:
user = UserAuth.objects.filter(access_key=acces_key).user
auth.login(request, user)
except:
pass
You don't want to reinvent the wheel. Use oAauth, you can save access_token for the future.