I am going to show how to wrap the OAuth callback flow with an Angular promise, and how that will help create a clean and maintainable code. This is not a tutorial about OAuth, and I am assuming:

  • you already have a good knowledge about OAuth authentication and its concepts.
  • you already have some knowledge about promises in JavaScript.

OAuth Authentication workflow

OAuth is an open standard that allow third-party providers like Facebook and Google to provide authentication service for your application.
In our example, we are going to use Authorization Code Flow, and using Facebook login as an example. The flow in our application will go like this:

  1. Start from our application login page:
    Our login page will have buttons to login with facebook or twitter or other social media login:

    social media login

  2. Clicking on a button will open a popup window that navigate to our application login URL.
  3. On server side of our application, the login code communicates with OAuth authentication server (the Facebook server that host the service).
  4. The authentication server will send a confirmation:
    The confirmation is something like this:

    social media login

  5. The user could just close the window by clicking on the close button (X button).
  6. Or the user will chose to confirm or deny the request.
    In both cases, the authentication server will respond by calling back our server with a POST HTTP call that we should specify its URL when registering the application with Facebook. This POST HTTP call is called: Callback URL


The JavaScript client side code:

I am going to cover only the JavaScript side code, and not the server code. Anyway if you used a third party OAuth library then your server side code will be very minimal (only few lines). All programming languages has OAuth library (C#, Ruby, PHP, Python…).
There are three functionalities that we want to write in JavaScript.

  1. Code to open the popup window.
  2. Code on the callback URL to receive the response from the user, and the user token (which is the biggest complicated piece)
  3. We need as well to watch the popup window: Because the user could just click on the close window button without responding to the confirmation.


The Final Goal

We want to wrap the authentication process with a promise, and write a handler on when the process is successful or failed.
We want to have the promise like this:

oauthService.authenticate(provider.url, provider.name)
        .then(function(response) {
          $log.debug('successful login');
          $log.debug(response);
          // do something with login
        })
        .catch(function(error) {
          $log.debug("didn't login");
          $log.debug(error);
        /* error could be one of the following:
          user denied
          user closed the window
          popup is blocked on the browser
          the authentication server failed for some reason
        */
        });
      
    };


Start writing the code

Let us start with open a popup window. We are going to write that as an Angular service:

function popupService ($window) {
  var service = {
     window : null,
     open : function(url, provider) {
        this.window = $window.open(url, '_blank', 'width=200,height=100'); 
     }
  };
  
  return service;
}
popupService.$inject = ['$window'];

It is better to take the string that has the window specs to an Angular constants like this:

function popupConstants () {
    facebook: 'width=580,height=400',
    gplus: 'width=452, height=633',
    twitter: 'width=495,height=645'' 
}

function popupService ($window, popupConstants) {
  var service = {
     window : null,
     open : function(url, provider) {
        this.window = $window.open(url, '_blank', popupConstants[provider]); 
     }
  };
  return service;
}

popupService.$inject = ['$window', 'popupConstants'];

angular.module('app')
    .constants('popupConstants', popupConstants)
    .service('popupService', popupService);

Now let us write the code that will check on the popup:
We have to write a code that regularly check if the window opened in the first step is still open, and we will use the JavaScript interval, but better we will use the Angular’s $interval which is a promise wrapper around the JavaScript native interval.

function popupService ($window, popupConstants, $rootScope, $interval) {
  var service = {
     window : null,
     localCheckPopup: null,
     open : function(url, provider) {
        this.window = $window.open(url, '_blank', popupConstants[provider]);
        this.observePopup();
        return true; 
     },
     checkPopup: function() {
        if (this.window.closed) {
            $interval.cancel(this.interval);
            // Notify the application that the window is closed by the user.
            $rootScope.$emit('window-closed');
        }
     },
     observePopup: function() {
       this.localCheckPopup = angular.bind(this, this.checkPopup);
        this.interval = $interval(function() {
            this.localCheckPopup();
        }, 500);
     }
  };
  return service;
}

Important points to notice from the above code

In the above code we created a function called observePopup that will run in interval 500 ms to check on the window.
The tip from the previous code is using angular.bind.
When the function checkPopup is called by the interval process, it will lose the “this” scope, because it is no longer called inside the object.
Angular’s bind is like calling:

checkPopup.apply(this)

What is still remaining is what we are going to use to notify the other code that the window was closed?
The convenient way is to use Angular’s $emit/$on for notification, and it is always better to use it on the $rootScope

Writing the OAuth callback JavaScript code:

The OAuth authentication server (The Facebook server in our case), will response back when we answer the confirmation page above, by calling a HTTP POST URL, that we registered when we register the application with Facebook.
We should have server code that will handle the POST, but on our client side we can write JavaScript code on the html page that will return from the POST URL, to notify our JavaScript code that the user logged in or rejected.
As I mentioned, I am not covering the server side code, but I will show how to pass the user token we got from the Facebook server into JavaScript.
Assuming that we are using Asp.NET MVC, and assume that in the POST callback controller handler we receive the user token in a .NET object variable called “user”, we can serialize the object to JSON, and pass it to JavaScript through Razor template like this:

var data = somedata;
var viewModel = new ViewModel();
var serializer = new JavaScriptSerializer();
viewModel.JsonData = serializer.Serialize(data);

return View("viewname", viewModel);

And on the view side, we add this JavaScript code:

var user = <%= Model.JsonData %>

Hook up the OAuth page with our application

Hooking up the above JavaScript user with our application is not easy.
Because the popup window was redirected to the facebook authentication website, it cannot communicate with our JavaScript code directly.
The only way to communicate between windows that belong to different domains is to use JavaScript window.postMessage.
But we even don’t know and don’t have a handle to the calling window (our application window).
The only connection we have is when we open the window, we got a handle to the popup window, which we can communicate with the popup window.
To fix this problem, we will do the following:

  • From Angular code, we will get a handle to the popup window.
  • Use the popup handle to postMessage sending the Angular App window as parameter.
  • Because we don’t know when the OAuth callback will be executed, so we have to keep repeating sending the postMessage with a parameter requestLogin.
    and the best way to do it is to hook it with the obeservePopup function that we showed before.
    We add inside the interval function the following line:
    service.window.postMessage('requestLogin', '*');
  • On the POST callback JavaScript side we listen to the postMessage with the addEventListenr.
  • When the OAuth facebook server callback the POST URL, and execute the JavaScript, the code will receive the message with the Angular App window, wich.


The code on the OAuth callback page will be:

window.addEventListener('message', function(e) {
            if (e.data === 'requestLogin') {
                e.source.postMessage({
                    message : 'loginSuccess',
                    userLogged: <%= Model.JsonData %>
                }, '*');
                window.setTimeout(window.close, 500);
            }
        });

The function observePopup will be:

obeservePopup: function() {
            this.interval = $interval(function() {
                 if (service.window.closed) {
                     $interval.cancel(service.interval);
                     $rootScope.$emit('window-closed');
                 }
                 service.window.postMessage('requestLogin', '*');
            }, 500);
        }

As you see on the OAuth page callback, we are sending a message loginSuccess when the user confirm the login process.
When we receive this message from the Angular side, we should stop the interval that we built before. The final code for the popup service will be like this:

function($log, auth_popupConstants, $window, $interval, $rootScope) {
    var service = {
        window: null,
        interval: null,
        cancelObserver: function(e) {
            if (e.data.message && e.data.message === 'loginSuccess') {
                $interval.cancel(service.interval);
            }
        }, 
        open: function(url, provider) {
            $window.addEventListener('message', this.cancelObserver);
            var optionsString = stringifyOptions(prepareOptions(auth_popupConstants[provider], $window));
            this.window = $window.open(url, '_blank', optionsString);
            if (!this.window) {
                $interval.cancel(this.interval);
                return false;
            } 
            this.obeservePopup();
            return true;
        },
        obeservePopup: function() {
            this.interval = $interval(function() {
                 if (service.window.closed) {
                     $interval.cancel(service.interval);
                     $rootScope.$emit('window-closed');
                 }
                 service.window.postMessage('requestLogin', '*');
            }, 500);
        }
    };
    return service;
};

Using Angular Promises API

After we wrote all required code, let’s now go the fun part. Let us build the promise that will wrap the whole process.
When we dealing with promises, there are two distinctive types of APIs.

  1. Creating promises.
  2. Using promises.

And corresponding to the previous types, Angular organizes its $q API into two distinctive groups:

  1. The deferred API to create promises.
  2. The promise API to consume promises.

We already did mentioned that we want to wrap the whole process with a promise that we can consume it like this:

oauthService.authenticate(provider.url, provider.name)
        .then(function(response) {
          $log.debug('successful login');
          $log.debug(response);
          // do something with login
        })
        .catch(function(error) {
          $log.debug("didn't login");
          $log.debug(error);
        /* error could be one of the following:
          user denied
          user closed the window
          popup is blocked on the browser
          the authentication server failed for some reason
        */
        });
    };

The above code is the Promise API in the $q library.
Let us talk about the fun part and how to create a promise.
The deferred API is for creating a new promise.

It is not that common that we create a new promise, because Angular already provide a promise façade for most common JavaScript APIs that are using callbacks, like Ajax, timeout, interval…
But our OAuth callback flow is a great example to introduce a new promise.
I will just add the code to create that promise:

function oauthService($q, popupService, $window, $log, $rootScope) {
    var service = {
        deferred:null,
        loginSuccess: null,
        popupClosed: null,
        listen: function() {
            this.loginSuccess = angular.bind(this, this.handleLoginSuccess);
            this.popupClosed = angular.bind(this, this.handlePopupClosed);
            $window.addEventListener('message', this.loginSuccess, false);
            $rootScope.$on('window-closed', this.popupClosed);
        },
        handleLoginSuccess: function(e) {
            if (e.data.message && e.data.message === 'loginSuccess') {
                this.deferred.resolve(e.data.userLogged);
            }
        },
        handlePopupClosed: function() {
            this.deferred.reject('the popup was closed');
        },
        authenticate: function(url, provider) {
            this.deferred = $q.defer();
            this.listen();

            var opened = popupService.open(url, provider);
            if (!opened) {
                   this.deferred.reject('popup blocked by browser');
            }
            return this.deferred.promise;
        }
    };
    return service;
};   
Notes from the previous code
  • We create a promise with:
    this.deferred = $q.defer();
  • Then we define how we resolve the promise with:
    this.deferred.resolve(e.data.userLogged);

    Which is executed when we receive the message from the OAuth callback page.

  • we reject the promise: When we receive a notification from Angular $rootScope when the user close the OAuth window without confirming
  • Another reason to reject the promise:
    When the browser block the popup, then we cannot open a new window, and popupservice.open will return false;


And now we can use the oauthService promise as we mentioned above