Search code examples
djangobackbone.jsauthenticationunderscore.js-templating

Django + Backbone: underscore template for login / logout


I am building a SPA using Django and Backbone. Until now most of my templates have been on the Django side, but I'm now moving to templating with Backbone/Underscore. The only thing I'm unsure about is how to manage page reload with respect to authentication.

I do not have a separate login page, but rather a dropdown login form on the app's menu bar (a Bootstrap navbar), which makes $.ajax requests for login/out actions. I want to keep the app interface publicly available, and only render certain components (load, save, export buttons) when a user is logged in, hiding them when logged out. A page reload obviously has to be aware of whether the user is logged in or not. This is how I'm managing it in my Django template:

<nav class="navbar navbar-default navbar-fixed-top">
   <div class="container">
      <!-- Menu left -->
      <ul class="nav navbar-nav">
         <!-- li components... -->
      </ul>
      <!-- Menu right -->
      <ul class="nav navbar-nav pull-right" id="navbar-right">
         {% if user.is_authenticated %}
         <!-- If user is logged in render 'My Account' and 'Logout' components -->
         <li id='menu-account'><a href='#'>My Account</a></li>
         <li id='menu-logout'><a href='#'>Logout</a></li>
         {% else %}
         <!-- If logged out render the login form -->
         <li id="menu-register"><a href="#">Register</a></li>
         <li class="dropdown" id="menu-login">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown"  role="button" aria-haspopup="true" aria-expanded="false" id="nav-login">Login</a>
            <div class="dropdown-menu pull-right">
               <form role="form" id="form-login" action="login/" method="POST">
                  <input class="form-control" name="username" id="username" placeholder="Username" type="text"><br>
                  <input class="form-control" name="password" id="password" placeholder="Password" type="password"><br>
                  <button type="submit" id="btn-login" class="btn btn-default">Login</button>
               </form>
            </div>
         </li>
         {% endif %}
      </ul>
   </div>
</nav>

This works very nicely, with the Django template tags taking care of the conditional rendering. An underscore version likely wouldn't look much different, but how do I determine whether the user is logged in/out from the client side? I'm thinking of just adding a response header for this, but is there a built-in Django way of doing it? I've looked at the login_required view decorator, but that seems to require a redirect for when the user is logged out.


Solution

  • You can manage authentication through tokens.

    var Account = Backbone.Model.extend({
        defaults: {
            authToken: undefined
    }
    

    In Account model you have methods for login and logout. In login method you pass username, and password params as a data to make request:

    login: function(username, password) {
        var self = this;
        return $.ajax({
                url: someUrl
                method: "POST",
                contentType: "application/json",
                data: JSON.stringify({
                    username: username,
                    password: password
                })
            }).done(function(data) {
                    console.log("login successful, saving auth token");
                    localStorage.authToken = data.token;
                    self.set("authToken", data.token);
            })
    }
    

    After request is finished we get promise callback where we get token and set it to localStorage.authToken data. Now we can have another method for checking user for authentication.

    isAuthenticated: function() {
        return (this.get("authToken") !== undefined);
    }
    

    And if you want to make logout to work, just delete authToken from localStorage:

    logout: function() {
        this.set("authToken", undefined);
        delete localStorage.authToken;
    }
    

    In request header you can have authorization through token or username/password combination. In your app.js we can override Backbone.sync method to inject Authorization header, so all of Backbone sync calls are authorized by default.

    var backboneSync = Backbone.sync;
    Backbone.sync = function (method, model, options) {
        if (account.isUserAuthenticated()) {
            options.headers = {
                "Authorization": "Token " + account.get("authToken")
            };
        }
        return backboneSync(method, model, options);
    };
    

    This is example of Token authentication - user enter his username and password in order to obtain a token. Token in stored on client-side (localStorage). So whole logic is to check if we have authToken property. Maybe this can guide you to right solution.