Using Authentication in an Aurelia App

In my last post, I added authentication to my Aurelia application by adding an Auth0 Lock process. I can sign in and sign out, but I’m not yet utilizing the information of whether I am authenticated or not. I can authenticate with Microsoft, Twitter, Facebook and Google+, or any number of authentication sources that Auth0 supports (which is a long list). I can still get to all the routes I defined (the home page and the Flickr photo review).

What I would like to do is to authenticate the flickr page. When I am signed in, the Flickr link appears on the top bar and I can click it and it acts normally. When I am signed out, the Flickr link does not appear on the top bar. If I go to the URL that I know, I want an alert telling me to log in.

Authentication

Aurelia has a pipeline structure for the navigation pipeline. When a request comes in, the request is first passed through a series of extensibility steps where you can add your own code. There are two steps defined by default – authorize and modelbind. You can define your own as well and fit them into the pipeline. Authorize comes before modelbind. I can use this to authenticate users.

First of all, I created a new class called AuthorizeStep. I placed this class in the app.ts file since it is relatively small. I basically followed the instructions in the Aurelia documentation substituting my logged-in check for the blank code:

class AuthorizeStep {
    run(routingContext, next) {
        if (routingContext.nextInstructions.some(i => i.config.auth)) {
            var isLoggedIn = AuthorizeStep.isLoggedIn();
            if (!isLoggedIn) {
                alert("Not Logged In!\nClick the Sign In icon to log in");
                return next.cancel();
            }
        }
        return next();
    }

    static isLoggedIn(): boolean {
        var auth_token = localStorage.getItem("auth_token");
        return (typeof auth_token !== "undefined" && auth_token !== null);
    }
}

The isLoggedIn() method checks for my auth_token is local storage and returns a boolean – the user is either logged in or not. I made this method static so other things can use this check just by using the AuthorizeStep.isLoggedIn() method. The run() method is a duplicate of the method within the documentation. I’ve just added a check to my new isLoggedIn() method (highlighted) and changed the redirect to a login page to an alert.

Note that I am checking the configuration of the route for a field called auth – if it’s there and it’s true then the authorize check is run. That means I need to adjust the route in the App class to trigger it. I also need to add the AuthorizeStep class to the router configuration:

export class App {
    public router: any;

    configureRouter(config, router) {
        config.title = 'Aurelia';

        config.addPipelineStep('authorize', AuthorizeStep);

        config.map([
            { route: 'welcome', name: 'welcome', moduleId: './pages/welcome', nav: true, title: 'Welcome' },
            { route: 'flickr', name: 'flickr', moduleId: './pages/flickr', nav: true, auth: true, title: 'Flickr' },
            { route: '', redirect: 'welcome' }
        ]);
        this.router = router;
    }
}

When you run this project, you will note that the Flickr link still appears. However, clicking on Flickr tells you to log in. I could, of course, emulate the sign-in logic by simulating a login button. For example:

    run(routingContext, next) {
        if (routingContext.nextInstructions.some(i => i.config.auth)) {
            var isLoggedIn = AuthorizeStep.isLoggedIn();
            if (!isLoggedIn) {
                console.log("User is not logged in - clicking on app-authenticator");
                var authButton = <HTMLDivElement>(document.getElementById("app-authenticator"));
                authButton.click();
                return next.cancel();
            }
        }
        return next();
    }

In this case, I am simulating a click on the DIV element within the app-authenticator. This then pops up the authentication screen that then authenticates the user prior to accessing the Flickr page. I like this functionality for “entry-points” – where you want to show a small number of entry pages. The user doesn’t have to sign in. When they access a restricted area, they are automatically prompted to sign in.

Navigation

It’s all well and good to be showing off the links then asking for authentication. However, maybe I want to hide the authenticated pages unless the user is signed in. Aurelia has a navigation data model that it constructs from the route map. It is triggered by the nav property on each route. So far, all our routes have had nav: true on them.

However, if we adjust the nav to be the value of AuthorizeStep.isLoggedIn(), then the links will disappear from the navigation model when the user is not logged in. I can adjust my route configuration as follows:

        config.map([
            { route: 'welcome', name: 'welcome', moduleId: './pages/welcome', nav: true, title: 'Welcome' },
            { route: 'flickr', name: 'flickr', moduleId: './pages/flickr', nav: AuthorizeStep.isLoggedIn(), auth: true, title: 'Flickr' },
            { route: '', redirect: 'welcome' }
        ]);

However, we do introduce a bug here. Run this project, sign in and note that the Flickr link appears. Then sign out. Note that the Flickr link does not disappear. Refresh the screen and note that the link is gone. In short, the navigation does not get redrawn when the user signs out.

There are a few ways to deal with this bug. A complicated mechanism would be to adjust app-authenticator and nav-bar so that they know about one another. When the user clicks on sign out, the app-authenticator sends a signal to the body of the document that says “signed out”. The nav bar can then listen to that and delete or hide the li elements that are authenticated. One can get these by using the ${row.config.auth} to get the configuration of the route. I’m not a fan of the complicated methodologies and will always opt for something simpler over complicated if it gets the job done.

My method is a little more straight forward. One needs to reload the page after removing the token from localStorage. Since the token doesn’t exist on the reload, the nav bar will go back to normal.

Simply use history.go(0) to refresh the screen. For some reason, window.location.reload() nor adjusting the window.location.href worked in my testing on Chrome. Using the history worked for all the browsers I tried. The click() method in app-authenticator.ts now looks like the following:

    click() {
        console.log("[AppAuthenticator] click ");
        let auth_token = localStorage.getItem("auth_token");
        if (auth_token) {
            // We clicked on the sign-out sign
            localStorage.removeItem("auth_token");
            history.go(0);
        } else {
            // We clicked on the sign-in sign
            localStorage.setItem("auth_redirect", window.location.href);
            this.lock.show({
                authParams: {
                    scope: "openid"
                }
            });
        }
    }

With in-browser cache, the reload of the application should not be too painful.

Of course, the fine folks that write Aurelia could choose to do something with authorize such that I can bind the isLoggedIn() method and the nav-bar can auto-update when I log out. This saves a page reload, which isn’t a big thing in the first place – it’s not like I’m reloading the application all the time.

Wrap Up

What I’ve done in this article is provide two mechanisms for page hiding within Aurelia. The first is the utilization of the authorize pipeline and the second is to adjust the visibility of links within the nav-bar. Both are valid and should be used in conjunction to secure your application.

I have not done any data security yet. In the next article I will investigate grabbing authenticated data from ASP.NET application, secured by the JSON Web Token. Until then, you can get the code for this article from my GitHub Repository.