Django: Login Form on Every Page

Up to the point, when it has come to Django user authentication and registrations, I’ve used django-registration as a plug and play solution. Authentication was frankly something I didn’t want to dive into, until now.

It turns out it is not scary at all. For my latest project, I had some space to fill in a free template I found and liked, so I decided to put a login form on every page if the user was not logged in. This is something I have done before in a PHP/Smarty project, but have yet to attempt in Django. It wasn’t totally straight forward, so I decided to document it here.

First of all, I put my login form in my base template. I just hand coded the form because I didn’t want to message with passing a form object to every single page. My login form code looked something like this:

{% if user.is_authenticated %}
<!-- Authenticate account menu -->
{% else %}
<h3>Login</h3>
<form action="/login/" method="post" accept-charset="utf-8">
<label for="username">Username</label><input type="text" name="username" value="" id="username" />
<label for="password">Password</label><input type="password" name="password" value="" id="password" />
<p><input type="submit" value="Login →"></p>
</form>
{% endif %}

I created my own login and logout views as follows:

def mylogin(request):
  if request.method == 'POST':
    user = authenticate(username=request.POST['username'], password=request.POST['password'])
    if user is not None:
      if user.is_active:
        login(request, user)
        # success
        return HttpResponseRedirect('/')
      else:
        # disabled account
        return direct_to_template(request, 'inactive_account.html')
    else:
      # invalid login
      return direct_to_template(request, 'invalid_login.html')
      
def mylogout(request):
  logout(request)
  return direct_to_template(request, 'logged_out.html')

And the URL patterns to go with them:

(r'^login/$', 'minisitetracker.sites.views.mylogin'),
(r'^logout/$', 'minisitetracker.sites.views.mylogout'),

This all worked fine, except when I wanted to use the @login_requied decorator, which redirected to /accounts/login/?next=(page I was on). So I created a simple redirect which basically told them to use the login form to login.

urlpatterns += patterns('django.views.generic.simple',
 &nbsp(r'^accounts/login/$', 'direct_to_template', {'template': 'login_required.html'}),
)

Notice the “next” variable that gets passed in though. This is a handy feature that sends the user to the page they were trying to access after they log in. Initially I thought it would be nice to have them stay on the page they were on once logging in, but I didn’t want to mess with figuring it out so I ignored it. But I just couldn’t ignore the “next” feature. It’s not in my nature.

So in PHP and Smarty, it is very easy to get POST and GET variables passed into the page from within the template such as { $smarty.post.var } or { $smarty.get.var }. These variables are not passed to Django automatically. So, for the first time, I wrote my own context processor:

def login(request):
  if 'next' in request.GET:
    return { 'NEXT': request.GET['next'] }
  else:
    return { 'NEXT': request.path }

Notice the else statement. Return the request.path by default also allowed me to keep the user on the same screen that he logged in on. Notice the slight update to my login form and function:

{% if user.is_authenticated %}
<!-- Authenticate account menu -->
{% else %}
<h3>Login</h3>
<form action="/login/" method="post" accept-charset="utf-8">
<label for="username">Username</label><input type="text" name="username" value="" id="username" />
<label for="password">Password</label><input type="password" name="password" value="" id="password" />
<input type="hidden" name="next" value="{{ NEXT }}" />
<p><input type="submit" value="Login →"></p>
</form>
{% endif %}}

def mylogin(request):
  if request.method == 'POST':
    user = authenticate(username=request.POST['username'], password=request.POST['password'])
    if user is not None:
      if user.is_active:
        login(request, user)
        # success
        if request.POST['next']:
          return HttpResponseRedirect(request.POST['next'])
        else:
          return HttpResponseRedirect('/')

      else:
        # disabled account
        return direct_to_template(request, 'inactive_account.html')
    else:
      # invalid login
      return direct_to_template(request, 'invalid_login.html')

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

March 31, 2009 · Dustin · 29 Comments
Tags:  · Posted in: Programming & Internet

  • http://905pm.com AJ

    That’s an interesting approach to your problem.
    Just so you know, the @login_required decorator actually redirects to settings.LOGIN_URL, which is set to /accounts/login/ by default.
    So setting LOGIN_URL = ‘/login/’ in your settings.py module would do the trick and you can get rid of the extra urlpattern.

  • http://www.exchangerates24.com/ rates

    It’s always useful to have a login form on every page.

  • http://www.dougalmatthews.com/ Dougal Matthews

    if you need to “pass a form object to every single page” or any other variable/object to every page you would use a context processor and then its taken care of you and you don’t need to manually pass it again :)

  • http://www.nerdydork.com Dustin

    @AJ: Thanks, I knew there was a simple way to change the default, but I was too lazy to look it up :D

    @Dougal: Yeah, you’re right. But when I made the decision I didn’t know abut context processors until about 20 minutes later when I found I needed to write one to get the ‘next’ GET variable. It works fine either way so I didn’t bother fixing it.

  • http://djangotricks.blogspot.com/ Aidas Bendoraitis [aka Archatas]

    If you put “django.core.context_processors.request” to your TEMPLATE_CONTEXT_PROCESSORS, then you don’t have to set the NEXT variable. You can just write something like this:

  • http://djangotricks.blogspot.com/ Aidas Bendoraitis [aka Archatas]

    That was “{% firstof request.POST.next request.path %}” as a value of the hidden field.

  • http://www.philippbosch.de/ Philipp Bosch

    If you used “django.contrib.auth.views.login” you would not need a context processor and could just write {{ next }} in your template.

  • http://www.nerdydork.com Dustin

    @Philipp – But that is just for that page right, you can’t use the {{ next }} any page, which was the point of this post, right? … or am I missing something?

  • http://www.philippbosch.de/ Philipp Bosch

    You’re probably right. Now I got what you wanna do.

  • http://www.asinox.net Asinox

    Hi, im new with Django, very nice post.

    My problem is that @login_required is not redirecting to the login page.. and i have all the setting thing fine in the Setting files…

    Nice Post :)

  • Jan Ostrochovsky

    Hello,

    I do not understand where to put that context processor and why it has the same name as built-in login() method, but without second parameter (user), only the first (request). Please, help. Thank you.

    Jano

  • http://www.nerdydork.com Dustin

    @Jano: I created a file called context_processors.py, basically the code you see above is all that is in that file.
    See the docs for more info: http://docs.djangoproject.com/en/dev/ref/templates/api/#writing-your-own-context-processors

  • Tibor Beck

    Hmmm, I don’t understand, why there is a need for hidden field next or other facilty. What about is the whole discussion? There is no need for next attribute, nor RequestContext or any other trick. Try simple this one:

    def login_view(request):
    if request.method == ‘POST’:
    user = authenticate(username = request.POST['login_username'], password = request.POST['login_password'])
    if user is None:
    return direct_to_template(request, ‘invalid_login.html’)
    if not user.is_active:
    return direct_to_template(request, ‘inactive_account.html’)
    login(request, user)
    try:
    return HttpResponseRedirect(request.META.get(‘HTTP_REFERER’, None))
    except KeyError:
    return HttpResponseRedirect(‘/’)

    def logout_view(request):
    logout(request)
    try:
    return HttpResponseRedirect(request.META.get(‘HTTP_REFERER’, None))
    except KeyError:
    return HttpResponseRedirect(‘/’)

  • http://www.nerdydork.com Dustin

    Nice Tibor. Looking at the code it seems rather obvious now :) Truth is, I wasn’t sure how to get the HTTP_REFERER in python/django.

  • Tibor Beck

    a little corection

    the try – except is useless because the get(HTTP_REFERER, None) returns None if the key doesn;t exists

    so the code can look like this:

    return HttpResponseRedirect(request.META.get(’HTTP_REFERER’, None) or ’/’)

  • reese

    It would suffice to put the form code in the base template with some slight changes:

  • reese

    The template code got stripped. It was

    change the action code:
    action=”{% url django.contrib.auth.views.login %}”

    and then the next code
    value=”{{ request.get_full_path }}”

    This way you can use the builtin view.

  • http://www.baolimotuo.cn 暴力摩托下载

    the try – except is useless because the get(HTTP_REFERER, None) returns None if the key doesn;t exists

  • http://simpleit.us Viet

    If you rely on ?next=URL and hidden field, someone can exploit that and redirect users to their site after successful login at your site. Better check for origin of the URL. Using HTTP_REFERER also can be exploited. Make sure that HTTP_REFERER and ?next=URL are from your site.

  • Anonymous

    It’s always useful to have a login form on every page.iphone 5

  • http://www.farmvillenews.net FarmVille News

    Lots of great info on the subject. Thanks for sharing!

  • http://tivimania.blogspot.com/ Tivi Mania

     if you need to “pass a form object to every single page” or any other
    variable/object to every page you would use a context processor and then
    its taken care of you and you don’t need to manually pass it again :)

  • http://tivimania.blogspot.com/ Tivi Mania

     if you need to “pass a form object to every single page” or any other
    variable/object to every page you would use a context processor and then
    its taken care of you and you don’t need to manually pass it again :)

  • baptist churches in austin tx

    This is such a great resource that you are providing and you give it away for free. I love seeing websites that understand the value of providing a quality resource for free. Thanks for this wonderful resource!
    Type your comment here.
    churches georgetown tx

  • baptist churches in austin tx

    This is such a great resource that you are providing and you give it away for free. I love seeing websites that understand the value of providing a quality resource for free. Thanks for this wonderful resource!
    Type your comment here.
    churches georgetown tx

  • Prabhat

    Authenticate not working i am getting mad can u help

  • http://christmasflowersinfrance.wordpress.com/ Christmas Flowers france

    I am
    absolutely amazed at how terrific the stuff is on this site. I have saved
    this webpage and I truly intend on visiting the site in the upcoming days.
    Keep up the excellent work!

  • http://christmasflowersfrance.blogspot.com/ Christmas Flowers france

    I am
    absolutely amazed at how terrific the stuff is on this site. I have saved
    this webpage and I truly intend on visiting the site in the upcoming days.
    Keep up the excellent work!

  • http://twitter.com/bm_ Jan

    followed your example but getting

    “CSRF token missing or incorrect.”

    guess they added more xss security in the recent django versions.