Authentication made simple in Single Page AngularJS Applications
The following is a login pattern that I’ve been using in all of my single page AngularJS applications (SPA). We recently introduced it into a client project at Brewhouse, so I thought I would share.
Login on an SPA can be tough and it’s important that your integration doesn’t interfere with the flow of your application. It can become a crutch if you’re not really sure whether the user is authenticated and resorting to explicit checks all over the place. Here we lay the groundwork for a login modal that is called implicitly only when it’s needed, without almost any knowledge from the rest of the application.
There are only three points in the application where the login modal should appear:
- When I’m on a welcome page and I click “Login”.
- When I am not logged in and I attempt to visit a page that requires login, e.g. my profile page.
- When I attempt to make a request that requires a login, e.g. my session has expired whilst I’m attempting to post something.
We will ignore the first condition as it is trivial.
Determining which pages require a logged in user
There are many advantages to using
ngRoute and in this case we will be taking advantage of how we can attach additional properties to a route and how those properties will cascade
down to the children of that route. In the following code, we make sure to set a
requireLogin property for each state.
Clearly, for routes that do not require a login we set
requireLogin to false. We could leave this
undefined, but I prefer to show my intent.
Capturing attempted state changes
At this point, we can start capturing attempted state changes and inspecting them for our
requireLogin property. The following shows how we are going to subscribe to
As you can see, if the route requires a login and
$rootScope.currentUser is not yet set, we will prevent the attempted state change and show the modal. (I prefer to attach the
currentUser to the
$rootScope only because it’s an application-wide
concept. All new scopes will inherit the
currentUser property and I can always call it from the view. Whether or not you choose to do the same is for another discussion.)
Additionally, it should be noted that I won’t be covering how you might store or retrieve the
currentUser between page refreshes.
Building the loginModal service
In this post, I will wrap the AngularUI Bootstrap Modal; however, you can use any modal library - I have even used the same pattern in an ionic app
$ionicModal service. An important caveat here is that we must be able to tell the modal what to do after authentication is successful or cancelled. Preferably this is done with a promise, so that
multiple actions can be chained together. These are anonymous functions that we will use to re-submit state changes and requests after authenticating. The following is a example of how I might write such a wrapper for the
There isn’t much magic in this service. It’s important to point out that
$modal.open returns a promise which is either resolved or rejected when the modal is closed or dismissed. When a scope is instantiated for the
$dismiss() methods are attached to it.
After a successful login, the modal is closed, the
$rootScope.currentUser is set, and the on success action will be triggered. If the user clicks the cancel button or attempts to click away from the modal, the on cancel action will step in.
Checking in with the state change
At this point the
loginModal can be added to the event listener. What makes this approach so slick is being able to capture the desired state transitions in anonymous functions. After authenticating, the SPA will implicitly redirect to the requested state as you would expect with a traditional web app. Additionally, notice how we will redirect to the welcome screen if a dismissal happens.
Handling unauthorized requests
currentUser on the client for the lifetime of an SPA is not without its challenges. Data that is persisted in memory may become out of sync with the server and a
401 will mean your client-side authentication
is no longer valid. Perhaps the session has expired or perhaps you were never logged in to begin with. This is obviously a good time to capture the request params and show the login modal.
AngularJS makes intercepting incoming responses pretty easy with interceptors. Below we call the login modal when we receive a
401 response. (For the sake of brevity, we’ll assume that a
401 is the only precondition required to verify that a user is not logged in.)
With this code, a
401 rejected request will be re-submitted when a successful authentication occurs. In fact, the entity that made the original request will hang until our secondary request is completed. It’s completely oblivious to the fact that the original request was even rejected!
If you’re not totally jacked up right now, I feel bad for you. The modal is so clean and separated. It’s effectively a black box. What more could you want? The use of anonymous callbacks to capture the user’s intent is key to this approach. With very little code we’ve integrated a login modal that is almost completely detatched from the rest of our application, helping to minimize the number of moving parts required for authentication.
Special thanks to Godfrey Chan, Philippe Creux and Lana Topham for leaving feedback during the drafting of this post.
Edit: December 29, 2014
As per a few requests, here is what a simple
views/loginModalTemplate.html might look like. I hope it helps to answer some questions.
Edit: Feburary 12, 2015
Godfrey did a write-up about implemented the same thing using Ember.js to help compare the two frameworks. If you enjoyed this blog post, you might like that too!