Implementing HTTP Basic Authentication

Let's implement the Basic Auth protocol in Contacts App. As you have learned in the previous sections, you will need to add the Authorization header for every request that you make to the server in order to be authenticated. From the server side, you will need to read and parse this header.

A useful npm package to decode the Authorization header has been developed. With the basic-auth module, you can read the request headers and return an object with two fields: name and pass, these fields can be used to authenticate the user. For simplicity, we will use a hardcoded user and password, not a real database:

// server/basicAuthMiddleware.js
var basicAuth = require('basic-auth');

var authorizationRequired = function (req, res, next) {
  var credentials = basicAuth(req) || {};

  if (credentials.name === 'john' && credentials.pass === 'doe') {
    return next();
  } else {
    return res.sendStatus(401);
  }
};

module.exports = authorizationRequired;

The middleware checks whether the user is john and the password is doe. If not, an HTTP 401 error will be sent to the client. You can use the middleware for each resources that you want to protect:

var controller = require('./controller');
var authorizationRequired = require('./basicAuthMiddleware');


module.exports = routes = function(server) {
  server.post('/api/contacts',
authorizationRequired, controller.createContact);
  server.get('/api/contacts',
authorizationRequired, controller.showContacts);
  server.get('/api/contacts/:contactId',
authorizationRequired, controller.findContactById);
  server.put('/api/contacts/:contactId',
authorizationRequired, controller.updateContact);
  server.delete('/api/contacts/:contactId',
authorizationRequired, controller.deleteContact);
  server.post('/api/contacts/:contactId/avatar',
authorizationRequired, controller.uploadAvatar);
};

The WWW-Authenticate header that we include in the HTTP 401 response will make sure that the browser prompts a dialog box asking you for a user and password. You can use the john user and the doe password in the dialog, then the browser will build and send the Authentication header for you:

Implementing HTTP Basic Authentication

Figure 10.7 Basic authentication login

To have more control over how to ask for authentication, you can create a form view and add some routes for authentication purposes:

<div class="col-xs-12 col-sm-offset-4 col-sm-4">
<div class="panel">
<div class="panel-body">
<h4>
Login required
</h4>
<p>
Use 'john' as user and 'doe' as password.
</p>
<form>
<div class="form-group">
<label for="username">User</label>
<input type="user" class="form-control" id="username" placeholder="Username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" placeholder="Password">
</div>
<p id="message" class="pull-left"></p>
<button type="submit" class="btn btn-primary pull-right">Login</button>
</form>
</div>
</div>
</div>

The LoginView method should handle the authentication process when the user clicks the Login button:

// apps/login/views/loginView.js
'use strict';

var Common = require('../../../common');
var template = require('../templates/login.tpl');

class LoginView extends Common.ModelView {
  constructor(options) {
    super(options);
    this.template = template;
  }

  get className() {
    return 'row';
  }

  get events() {
    return {
      'click button': 'makeLogin'
    };
  }

  makeLogin(event) {
    event.preventDefault();

    var username = this.$el.find('#username').val();
    var password = this.$el.find('#password').val();

console.log('Will login the user', username,
                'with password', password);
  }
}

module.exports = LoginView; 

A new route should be added to show the #/login form:

// apps/login/router.js
'use strict';

var Backbone = require('backbone');
var LoginView = require('./views/loginView');

class LoginRouter extends Backbone.Router {
  constructor(options) {
    super(options);

    this.routes = {
      'login': 'showLogin'
    };

    this._bindRoutes();
  }

  showLogin() {
    var App = require('../../app');
    var login = new LoginView();

    App.mainRegion.show(login);
  }
}

module.exports = new LoginRouter();

You will need to include this new router when the application bootstraps, as follows:

// app.js
// ...

// Initialize all available routes
require('./apps/contacts/router');
require('./apps/login/router');

// ...

When an unauthenticated user accesses the #/contacts route, Backbone Application should redirect them to the login form:

Backbone.$.ajaxSetup({
  statusCode: {
    401: () =>{
      window.location.replace('/#login');

    }
  }
});

When the server responds with an HTTP 401, it means that the user is not authenticated and you then can show the login window. Remember to remove the WWW-Authenticate response header in order to prevent the browser from showing its login dialog:

function unauthorized(res) {
  // res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
  return res.sendStatus(401);
};
Implementing HTTP Basic Authentication

Figure 10.8 Login form

Now that we have a login form in place, we can put the authentication code in it. That's going to be done in the following three steps:

  1. Build the Authentication string.
  2. Test whether the Authentication string is valid.
  3. Save the Authentication string for future requests.

The authentication string is easy to build, you can use the btoa()function to convert strings to base64, as follows:

class LoginView extends Common.ModelView {
  // ...

  makeLogin(event) {
    event.preventDefault();

    var username = this.$el.find('#username').val();
    var password = this.$el.find('#password').val();
    var authString = this.buildAuthString(
      username, password
    );

    console.log('Will use', authString);
  }

  buildAuthString(username, password) {
    return btoa(username + ':' + password);
  }
}

Then, you can use authString to test whether can get the contacts resource successfully. If the server answers successfully, then the user is using the right credentials:

class LoginView extends Common.ModelView {
  // ...

  makeLogin(event) {
    event.preventDefault();

    var username = this.$el.find('#username').val();
    var password = this.$el.find('#password').val();
    var authString = this.buildAuthString(
      username, password
    );

    Backbone.$.ajax({
      url: '/api/contacts',
      headers: {
        Authorization: 'Basic ' + authString
      },
      success: () => {
        var App = require('../../../app');
        App.router.navigate('contacts', true);
      },
      error: jqxhr => {
        if (jqxhr.status === 401) {
          this.showError('User/Password are not valid');
        } else {
          this.showError('Oops... Unknown error happens');
        }
      }
    });
  }

  buildAuthString(username, password) {
    return btoa(username + ':' + password);
  }

  showError(message) {
    this.$('#message').html(message);
  }
}

If the Authentication string is valid, then the user is redirected to the contact list; however, the redirection will not work as expected as the Authorization header in the contact list is not sent. Remember that you should send the Authorization header for every request.

You will need to save the Authentication string in sessionStorage to be used in future requests. The sessionStorage is similar to localStorage; however, in sessionStorage, the data will be removed when the browser is closed:

class LoginView extends Common.ModelView {
  // ...

  makeLogin(event) {
// ...

    Backbone.$.ajax({
      url: '/api/contacts',
      headers: {
        Authorization: 'Basic ' + authString
      },
      success: () => {
        var App = require('../../../app');
        App.saveAuth('Basic', authSting);
        App.router.navigate('contacts', true);
      },
      error: jqxhr => {
        if (jqxhr.status === 401) {
          this.showError('User/Password are not valid');
        } else {
          this.showError('Oops... Unknown error happens');
        }
      }
    });
  }

// ...
}

The App object will be responsible for storing the token:

// app.js
var App = {
  // ...

  // Save an authentication token
  saveAuth(type, token) {
    var authConfig = type + ':' + token;

    sessionStorage.setItem('auth', authConfig);
    this.setAuth(type, token);
  },

  // ...
}

After the token is saved in sessionStorage, you should include the Authorization header for every future request:

// app.js
var App = {
  // ...

  // Set an authorization token
  setAuth(type, token) {
    var authString = type + ' ' + token;
    this.setupAjax(authString);
  },

  // Set Authorization header for authentication
  setupAjax(authString) {
    var headers = {};

    if (authString) {
      headers = {
        Authorization: authString
      };
    }

    Backbone.$.ajaxSetup({
      statusCode: {
        401: () => {
          App.router.navigate('login', true);
        }
      },
      headers: headers
    });
  }

  // ...
}

When the application is bootstrapped, it should look whether there is an active session open; if so, it should use the session, as shown in the following:

// app.js
var App = {
start() {
    // The common place where sub-applications will be showed
    App.mainRegion = new Region({el: '#main'});

    this.initializePlugins();

    // Load authentication data
    this.initializeAuth();

    // Create a global router to enable sub-applications
    // to redirect to
    // other URLs
    App.router = new DefaultRouter();
    Backbone.history.start();
  },

  // ...

  // Load authorization data from sessionStorage
  initializeAuth() {
    var authConfig = sessionStorage.getItem('auth');

    if (!authConfig) {
      return window.location.replace('/#login');
    }

    var splittedAuth = authConfig.split(':');
    var type = splittedAuth[0];
    var token = splittedAuth[1];

    this.setAuth(type, token);
  },

  // ...
}

The user should be able to log out. Let's add a route for the user to log out in the App router:

// app.js

// General routes non sub-application dependant
class DefaultRouter extends Backbone.Router {
  constructor(options) {
    super(options);
    this.routes = {
      '': 'defaultRoute',
      'logout': 'logout'
    };
    this._bindRoutes();
  }

  // Redirect to contacts app by default
  defaultRoute() {
    this.navigate('contacts', true);
  }

  // Drop session data
  logout() {
    App.dropAuth();
    this.navigate('login', true);
  }
}

The session is removed when the auth string is removed from sessionStorage and the Authentication header is not sent anymore:

var App = {
  // ...

  // Remove authorization token
  dropAuth() {
    sessionStorage.removeItem('auth');
    this.setupAjax(null);
  },

  // …
}

That's how you can implement authorization with the HTTP Basic Auth protocol. An authorization string is generated and attached for every request made to the server, that's done with the help of the ajaxSetup()method of jQuery. In the following section, we will see how to implement the OAuth2 protocol.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.103.204