Django Login/Session Not Sticking - django

After my site has been up and running for a while in production, I suddenly have a problem with my users loging into it.
I have protected certain views/pages with the login_required decorator and I am also using the django admin.
When an anonymous user hits any of these pages, he is redirected to the login page.
When this anonymous user adds its credentials, the POST request is successful and he is redirected to the inital page. At the same time, the user gets a new sessionid (as expected)
However, now the results get very unreliable. When pressing reload or when navigating to other pages (that require a login), either of the 2 outcomes might happen:
a) The user is identified and the page is displayed correctly
b) The user is redirect to the login page.
I have checked the content of the session via the shell, and nothing is changing there.
The production site is served via a load balancer and 8 application servers. Even stranger: if I test the same code (with the same settings) on a test server, that is not load balanced and basically has not traffic, everything is working fine.
I am running Django 1.6 on Ubuntu with Apache and mod_wsgi in daemon mode behind SSL and I am using the Session database backend. I am using django-allauth.account for account management/login. My Session settings are like this:
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 60*60*24
SESSION_COOKIE_SECURE = True
UPDATE
To get some more debug information, I have created this Middleware:
from django.conf import settings
class SessionDebugMiddleware(object):
def process_response(self, request, response):
session = request.session
user = getattr(request, 'user', None)
if user:
user=user.id
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
response['X-Meta-Requ'] = '{0},{1},{2},{3}'.format(session_key, session.get('_auth_user_id'), session.get('_auth_user_backend','---'), user)
return response
If I hit the refresh button 10 times,
8 times I will get this header:
igv0xshezhdxh50kks9x00r0l67mx0sk,None,---,None
2 times I will get this header: X-Meta-Requ:igv0xshezhdxh50kks9x00r0l67mx0sk,330619,django.contrib.auth.backends.ModelBackend,330619
It seems be be random and does not follow any logic.
So I have the following questions/ideas?
Can this be related to loadbalancing in anyway? My understanding was that Django does not need sticky sessions, when the DB session backend is used.
Can this be related to a threading issue?
Can this be related to high load?
Can this be related to a decoding issue: https://github.com/django/django/blob/master/django/contrib/sessions/backends/base.py#L83. But why should that decoding issue not be consistent. And I have not found any log entries that refer to "Session data corrupted".
Any other hints are welcome.

Just in case someone is having this issue, it can also be caused by SECRET_KEY definitions not being consistent.
I had mine generated in the settings file (a bad decision).
It was fine in development mode, but once the application was served by WSGI, multiple instances were created, each with different keys.
This causes the mixup in the hash, and the corrupt session to be reported.

I think you have problem because of highload or error in your code (maybe you just rebuild session in your application anywhere and forget about it :)
Please try to read info from database with your session key to be sure session data exists.
If data exists but not loaded to session - this mean you have troubles with session processing. If data doesn't exists, but it present in past because you got 2 times right session data, this mean you have problems with session storage (maybe because of highload your database lose sessions data). Do you have database replication?
Also which session backend you use? Just a DB or Cached_Db?

Related

I changed the session cookie name in my django project and I am unable to login

I want to change the SESSION_COOKIE_DOMAIN because it's set incorrectly on a production server. Due to this, I also need to change the SESSION_COOKIE_NAME to something else. Otherwise, there'll be two cookies with the same name and different domains on the user's side, because it's already in production. With these two cookies, the user will have trouble logging in.
This would normally be fine, but now the problem.
Just after I changed the SESSION_COOKIE_NAME (so I haven't even changed the SESSION_COOKIE_DOMAIN yet), I am unable to login into the admin console. I can see the cookie with the new name being set. The application itself has no problems and users can login without problems, which is strange because it's the same authentication.
What I've tried:
Deleting all sessions on the server site
Private mode
Other Browser
Flushing all possible caches on the server but might have missed one.
Checking for hardcoded session names, but there are not.
What else could I possibly check? If I set the SESSION_COOKIE_NAME back to what it was, I can log in again. This issue only happens on the production server; not locally, and not on the test server, which makes it hard to test.
Any ideas?

Unable to login to Django admin after changing SESSION_COOKIE_NAME

I want to change the SESSION_COOKIE_DOMAIN because it's set incorrectly on a production server. Due to this, I also need to change the SESSION_COOKIE_NAME to something else. Otherwise there'll be two cookies with the same name and different domains on the users side, because it's already in production. With these two cookies, the user will have trouble logging in.
This would normally be fine, but now the problem.
Just after I changed the SESSION_COOKIE_NAME (so I haven't even changed the SESSION_COOKIE_DOMAIN yet), I am unable to login into the admin console. I can see the cookie with the new name being set. The application itself has no problems and users can login without problems, which is strange because it's the same authentication.
What I've tried:
Deleting all session on the server site
Private mode
Other Browser
Flushing all possible caches on the server but might have mist one.
Checking for hardcoded session names, but there are not.
What else could I possibly check? If I set the SESSION_COOKIE_NAME back to what it was, I can log in again. This issue only happens on the production server; not locally, and on not on the test server, which makes it hard to test.
Any ideas?

CSRF failure on server using Remote Authentication (works when cookie is cleared)

I'm using Django's RemoteUserMiddleware to authenticate the users in one website. It works great until the next day. When I try to do anything using POST methods (even in Django's admin page) it gives me a 403 error.
I clear the cookies and it starts to work again.
There's a firewall in between my browser and the server which sends the headers to the server (so Django can know who is logged in).
Django's version: 1.9.6
I'm using NGINX in my server
When the error occurs django changes the csrftoken each time. It seems it is getting an Anonymous User when the cache is not cleared...
Any thoughts what could be causing this weird behavior?
This was due one invalid cookie being injected by the system. Django doesn't handle well whenever there is an invalid cookie... it kind wipes some of the keys in the meta tag (where the cookies are checked)

How does one enforce automatic logout due to inactivity in a Django application?

In my Django application, I would like for the user to be automatically logged out after 30 minutes of inactivity, so I used this setting in settings.py:
SESSION_COOKIE_AGE = 1800
However, using this setting logs the user out in 30 minutes regardless of activity. How does one enforce automatic logout due to inactivity in a Django application?
As an update on this topic. Django now has the SESSION_SAVE_EVERY_REQUEST setting which makes it a lot easier.
django-session-security notes the user activity based on server side and javascript events such as mousemove, keypress, etc, etc ... Also, it warns the user before expiring the session, and tries not to expire the session (where there any activity maybe from another browser tab ?).
Just install it and set settings.SESSION_SECURITY_EXPIRE_AFTER=1800. You could also set settings.SESSION_SECURITY_WARN_AFTER=1740.
You could update the session of an user when he accesses your site. For example in a middleware, this force session to be set again.
class ActivateUser(object):
def process_request(self, request):
if request.user.is_authenticated():
request.session.modified = True

Can I use HTTP Basic Authentication with Django?

We have a website running on Apache, access to which has a number of static pages protected via HTTP Basic authentication.
I've written a new part of the site with Django using Django's built in support for user management.
The problem I have is that users have to log in once via the HTTP Basic authentication and then again using a Django login form. This both clumsy and very confusing for users.
I was wondering if anyone had found a way to make Django log a user in using the HTTP Basic authentication information.
I not expecting to pass a password to Django, but rather if a user dave has been authenticated by Apache then they should be automatically logged into Django as dave too.
(One option would be to make Apache and Django share a user store to ensure common usernames and passwords but this would still involve two login prompts which is what I'm trying to avoid.)
For just supporting basic auth on some requests (and not mucking with the web server -- which is how someone might interpret your question title), you will want to look here:
http://www.djangosnippets.org/snippets/243/
This has been added to the Django 1.3 release. See more current documentation for this here:
http://docs.djangoproject.com/en/dev/howto/auth-remote-user/
Do check out Oli's links. You basically see the authenticated username as verified by Basic HTTP Authentication in Django by looking at request.META['REMOTE_USER'].
Update: Tested the proposed patch for ticket #689, which is available up-to-date in telenieko's git repository here. It applies cleanly at least on revision 9084 of Django.
Activate the remote user authentication backend by
adding the RemoteUserAuthMiddleware after AuthenticationMiddleware
adding the setting AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.RemoteUserAuthBackend',)
If you use lighttpd and FastCGI like I do, activate mod_auth, create credentials for a test user (I called it testuser and set 123 as the password) and configure the Django site to require basic authentication.
The following urls.py can be used to test the setup:
from django.conf.urls.defaults import *
from django.http import HttpResponse
from django.contrib.auth.models import User
urlpatterns = patterns('',
url(regex='^$',
view=lambda request: HttpResponse(repr(request), 'text/plain')),
url(regex='^user/$',
view=lambda request: HttpResponse(repr(request.user), 'text/plain')),
url(regex='^users/$',
view=lambda request: HttpResponse(
','.join(u.username for u in User.objects.all()),
'text/plain')),
)
After reloading lighty and the Django FCGI server, loading the root of the site now asks for authentication and accepts the testuser credentials, and then outputs a dump of the request object. In request.META these new properties should be present:
'AUTH_TYPE': 'Basic'
'HTTP_AUTHORIZATION': 'Basic dGVzdHVzZXI6MTIz'
'REMOTE_USER': 'testuser'
The /user/ URL can be used to check that you're indeed logged in as testuser:
<User: testuser>
And the /users/ URL now lists the automatically added testuser (here the admin user I had created when doing syncdb is also shown):
admin,testuser
If you don't want to patch Django, it's trivial to detach the RemoteUserAuthBackend and RemoteUserAuthMiddleware classes into a separate module and refer to that in the Django settings.
Yes you can use basic autorization with django as something similar:
def post(self, request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
import base64
expected = base64.b64encode(b'<username>:<password>').decode()
if token_type != 'Basic' or credentials != expected:
return HttpResponse(status=401)
authorization success flow code ...
request.META contains key HTTP_AUTHORIZATION in which your Autorization is present.
In case if you are using apache with modWSGI, the key HTTP_AUTHORIZATION might not be present. You need to add below line in your WSGI config
WSGIPassAuthorization On
Refer this detailed answer:
Passing apache2 digest authentication information to a wsgi script run by mod_wsgi
Hope it is useful for someone who is wondering why HTTP_AUTHORIZATION key is not present
There is httpauth.py. I'm still a complete newb with Django so I've no idea how it fits in exactly, but it should do what you're looking for.
Edit: here's a longer bug thread on the subject.
Because django can be run in several ways, and only modpython gives you close integration with Apache, I don't believe there is a way for django to log you in basic on Apache's basic auth. Authentication should really be done at the application level as it'll give you much more control and will be simpler. You really don't want the hassle of sharing a userdata between Python and Apache.
If you don't mind using a patched version of Django then there is a patch at http://www.djangosnippets.org/snippets/56/ which will give you some middleware to support basic auth.
Basic auth is really quite simple - if the user isn't logged in you return a 401 authentication required status code. This prompts the browser to display a login box. The browser will then supply the username and password as bas64 encoded strings. The wikipedia entry http://en.wikipedia.org/wiki/Basic_access_authentication is pretty good.
If the patch doesn't do what you want then you could implement basic auth yourself quite quickly.
This seems to be a task for custom AuthenticationBackend - see Django documentation on this subject, djangosnippets.org has some real-life examples of such code (see 1 or 2) (and this is not really a hard thing).
AuthenticationBackend subclasses have to have only 2 methods defined and their code is pretty straightforward: one has to return User object for user ID, the second has to perform credentials check and return User object if the credentials are valid.