Backbone.js Tutorial: Authenticating with OAuth2

13 Dec 2012 | By Alex Young | Tags backbone.js mvc node backgoog

In Part 2: Google’s APIs, I laid the groundwork for talking to Google’s JavaScript APIs. Now you’re in a position to start talking to the todos API, but first a user account is required.

Preparation

Before starting this tutorial, you’ll need the following:

  • alexyoung / dailyjs-backbone-tutorial at commit 9d09a66b1f
  • The API key from part 2
  • The “Client ID” key from part 2
  • Update app/js/config.js with your keys (if you’ve checked out my source)

To check out the source, run the following commands (or use a suitable Git GUI tool):

git clone git@github.com:alexyoung/dailyjs-backbone-tutorial.git
cd dailyjs-backbone-tutorial
git reset --hard 9d09a66b1f

Google’s OAuth 2.0 Client-side API

Open app/js/gapi.js and take a look at lines 11 to 25. There’s a method, provided by Google, called gapi.auth.authorize. This uses the “Client ID” and some scopes to attempt to authenticate. I’ve already set the scopes in app/js/config.js:

config.scopes = 'https://www.googleapis.com/auth/tasks https://www.googleapis.com/auth/userinfo.profile';

This tells the authentication system that our application would like to access the user’s profile and Gmail tasks. Everything is almost ready to work, but two things are missing: an implementation for handleAuthResult and an interface.

Templates

RequireJS can load templates by using the text plugin. Download text.js from GitHub and save it to app/js/lib/text.js.

This is my preferred technique for handling templates. Although this application could easily fit into a monolithic index.html file, breaking up projects into smaller templates is more manageable in the long run, so it’s a good idea to get used to doing this.

Now open app/js/main.js and add the text plugin to the paths property of the RequireJS configuration:

paths: {
  text: 'lib/text'
},

Finally, add this to app/js/config.js:

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g
};

This tells Underscore’s templating system to use double curly braces for inserting values, otherwise known as interpolation.

The app needs some directories to store template-related things:

  • app/js/views – This is for Backbone.js views
  • app/js/templates – Plain HTML templates, to be loaded by the views
  • app/css

The app/index.html file needs to load the CSS, so add a link tag to it:

<link rel="stylesheet" href="css/app.css">

And create a suitable CSS file in app/css/app.css:

#sign-in-container, #signed-in-container { display: none }

The application will start up by hiding both the sign-in button and the main content. The oauth API will be queried for existing user credentials – if the user has already logged in recently their details will be stored in a cookie, so the views need to be configured appropriately.

Templates

The templates aren’t particularly remarkable at this stage, just dump this into app/js/templates/app.html:

<div class="row-fluid">
  <div class="span2 main-left-col" id="lists-panel">
    <h1>bTask</h1>
    <div class="left-nav"></div>
  </div>
  <div class="main-right-col">
    <small class="pull-right" id="profile-container"></small>
    <div>
      <div id="sign-in-container"></div>
      <div id="signed-in-container">
        <p>You're signed in!</p>
      </div>
    </div>
  </div>
</div>

This template shows some things that we won’t be using yet, just ignore it for now and focus on sign-in-container and signed-in-container.

Next, paste the following into app/js/templates/auth.html:

<a href="#" id="authorize-button" class="btn btn-primary">Sign In with Google</a>

The auth.html template will be inserted into sign-in-container. It’s very simple at the moment, I only really included it for an excuse to create extra Backbone.js views so you can see how it’s done.

Backbone Views

These templates need corresponding Backbone.js views to manage them. This part demonstrates how to load templates with RequireJS and render them. Create a file called app/js/views/app.js:

define([
  'text!templates/app.html'
],

function(template) {
  var AppView = Backbone.View.extend({
    id: 'main',
    tagName: 'div',
    className: 'container-fluid',
    el: 'body',
    template: _.template(template),

    events: {
    },

    initialize: function() {
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    }
  });

  return AppView;
});

The AppView class doesn’t have any events yet, but it does bind to an element, body, and load a template: define(['text!templates/app.html']. The text! directive is provided by the RequireJS “text” plugin we added earlier. The template itself is just a string that contains the corresponding HTML. It’s rendered by binding it to the Backbone.View, and then calling jQuery’s html() method which replaces the HTML within an element: this.$el.html(this.template());.

The AuthView is a bit different. Create a file called app/js/views/auth.js:

define(['text!templates/auth.html'], function(template) {
  var AuthView = Backbone.View.extend({
    el: '#sign-in-container',
    template: _.template(template),

    events: {
      'click #authorize-button': 'auth'
    },

    initialize: function(app) {
      this.app = app;
    },

    render: function() {
      this.$el.html(this.template());
      return this;
    },

    auth: function() {
      this.app.apiManager.checkAuth();
      return false;
    }
  });

  return AuthView;
});

The app object is passed to initialize when AuthView is instantiated (with new AuthView(this) later on). The reason I’ve done this is to allow the view to call the required authentication code from ApiManager. This could also be handled with events, or many other ways – I just wanted to show that we can initialise views with values just like any other class.

App Core

The views need to be instantiated and rendered. Open app/js/app.js and change it to load the views using RequireJS:

define([
  'gapi'
, 'views/app'
, 'views/auth'
],

function(ApiManager, AppView, AuthView) {
  var App = function() {
    this.views.app = new AppView();
    this.views.app.render();

    this.views.auth = new AuthView(this);
    this.views.auth.render();

    this.connectGapi();
  }

The rest of the file can remain the same. Notice that the order these views are rendered is important – AuthView won’t work unless it has some of AppView’s tags available. A better way of modeling this might be to move AuthView inside AppView so the dependency is reflected. You can try this yourself if you want to experiment.

Authentication Implementation

The app/js/gapi.js file still doesn’t have the handleAuthResult function, so nothing will work yet. Here’s the code to handle authentication:

function handleAuthResult(authResult) {
  var authTimeout;

  if (authResult && !authResult.error) {
    // Schedule a check when the authentication token expires
    if (authResult.expires_in) {
      authTimeout = (authResult.expires_in - 5 * 60) * 1000;
      setTimeout(checkAuth, authTimeout);
    }

    app.views.auth.$el.hide();
    $('#signed-in-container').show();
  } else {
    if (authResult && authResult.error) {
      // TODO: Show error
      console.error('Unable to sign in:', authResult.error);
    }

    app.views.auth.$el.show();
  }
}

this.checkAuth = function() {
  gapi.auth.authorize({ client_id: config.clientId, scope: config.scopes, immediate: false }, handleAuthResult);
};

The trick to a smooth sign-in flow is to determine when the user is already signed in. If so, then authentication should be handled transparently, otherwise the user should be prompted.

The handleAuthResult function is called by gapi.auth.authorize from the checkAuth function, which isn’t displayed here (it’s before handleAuthResult in the source file if you want to check it). The this.checkAuth method is different – this is a public method that calls gapi.auth.authorize with immediate set to false, while the other invocation calls it with true.

The immediate option is important because it determines whether a popup will be displayed or not. I’ve used it to check if the user is already signed in, otherwise it’s called again with immediate: false and will display a suitable popup so the user can see what permissions the app wants to use:

Authentication process

I designed it this way based on the Google APIs Client Library for JavaScript documentation:

“The standard authorize() method always shows a popup, which can be a little jarring if you are just trying to refresh an OAuth 2.0 token. Google’s OAuth 2.0 implementation also supports “immediate” mode, which refreshes a token without a popup. To use immediate mode, just add “immediate: true” to the login config as in the example above.”

I’ve also changed the ApiManager class to store a reference to App:

// Near the top of gapi.js
var app;

function ApiManager(_app) {
  app = _app;
  this.loadGapi();
}

Summary

In this tutorial you’ve seen how to use Google’s APIs to sign into an app you’ve previously registered with the Google API Console (documented in part 2). It might seem like a lot of work to get RequireJS, Backbone.js, and Google OAuth working together, but think about what this has achieved: 100% client-side scripting that can authenticate with existing Google accounts.

If I’ve missed anything here, you can check out the full source from commit c1d5a2e7c.


blog comments powered by Disqus