Sign Up
Hero

Creating your first Aurelia app: From authentication to calling an API

Learn how to create a real world Aurelia app using ES6, aurelia-auth and much more! We'll implement from Authentication to calling an API and everything in between

This post is out of date.

The Aurelia framework and its dependencies have undergone many changes since the publication of this article, and the method of authentication utilized in this post is not up-to-date with current best practices. Thank you for your patience while we work on bringing you an updated tutorial!


TL;DR: Aurelia is a great client-side JavaScript framework and adding JWT authentication to Aurelia apps is easy with the aurelia-auth package. Check out the GitHub repo for this article to find out how to add authentication to your Aurelia app.


Aurelia is a client-side JavaScript framework that has been gaining a lot of popularity lately. One of the nice aspects of Aurelia is that it anticipates common application needs and provides simple conventions for accomplishing them. In some ways, Aurelia is similar to Angular 2, so parts of it may look familiar if you've checked out Angular 2 already.

"Aurelia anticipates common application needs and provides simple conventions for accomplishing them."

Tweet This

Getting Started

Getting started with Aurelia is a piece of cake. The framework's getting started guide offers an in-depth set of instructions and a seed project that make it very simple to get up and running quickly.

Aurela has a cli tool. You can run npm install -g aurelia-cli to get the new cli tool. Then just run au new and the CLI will setup your project and you'll have an app ready for deploy within a few minutes. You can read more about it in the CLI documentation.

This tutorial will expand upon the seed project and show how to add JWT authentication to a random quote application. We'll be using the NodeJS JWT Authentication Sample as our backend to show how we can retrieve a JWT upon login, save it in local storage, and send it along with every subsequent request. Our app will let all visitors retrieve a random quote, but logged-in users will be able to get a super-secret quote.

Two Ways to Get Going

You can go through the getting started guide that Aurelia provides and follow along, changing files in their seed project as we go. Alternatively, you can clone the the project for this tutorial and follow along from there.

If you are starting from Aurelia's seed project, be sure to split it out into two subdirectories, client and server. The server directory is where the NodeJS JWT authentication sample will go.

To get the server setup, you can follow the instructions in the readme for the NodeJS JWT Authentication Sample.

A Little Help from Aurelia-Auth

To help us with our token dealings on the front-end, we'll use the awesome aurelia-auth plugin provided by Paul van Bladel, along with some pointers from his sample app repo.

Setting up the Client Config Files

There's a bit of configuration setup to do, but once it's in place, things will be a breeze.

After doing npm install and jspm install to pull in all the dependencies, we'll need to also install the aurelia-auth plugin. We can do this with jspm within the client directory:

jspm install github:paulvanbladel/aurelia-auth

Application Bootstrap Config

Let's give our app the name quotes-app and reflect this in the body tag of index.html where the app loads.

  <!-- client/index.html -->

  ...

  <body aurelia-app="quotes-app">
    <script src="jspm_packages/system.js"></script>
    <script src="config.js"></script>
    <script>
      System.config({
        "paths": {
          "*": "dist/*.js"
        }
      });
    </script>
    <script>
      System.import('aurelia-bootstrapper');
    </script>
  </body>

  ...

Aurelia looks for a JavaScript file with the same name in the src directory for the main app config details. Let's create that now:

// client/src/quotes-app.js

import config from './auth-config';

export function configure(aurelia) {

  // Here we provide configuration for our application and can
  // bring in the configuration settings we put within auth-config.js
  // that will tell the aureliauth plugin the specific settings
  // for our application's authentication context.
  aurelia.use
    .standardConfiguration()
    .developmentLogging()
    .plugin('paulvanbladel/aureliauth', (baseConfig) => {
         baseConfig.configure(config);
    });

  aurelia.start().then(a => a.setRoot());
}

You can see here that we're importing a file called auth-config.js and that it's the export from this file that is passed to the baseConfig for the plugin. The auth-config file will let us override the aurelia-auth plugin's defaults with our own specifics. Let's create it now:

// client/src/auth-config.js

// Specific settings for our application's
// authentication context. These will override
// the default settings provided by aureliauth

var config = {

  // Our Node API is being served from localhost:3001
  baseUrl: 'http://localhost:3001',
  // The API specifies that new users register at the POST /users enpoint.
  signupUrl: 'users',
  // Logins happen at the POST /sessions/create endpoint.
  loginUrl: 'sessions/create',
  // The API serves its tokens with a key of id_token which differs from
  // aureliauth's standard.
  tokenName: 'id_token',
  // Once logged in, we want to redirect the user to the welcome view.
  loginRedirect: '#/welcome',

}

export default config;

The API is accessible at localhost:3001, so we set this as our baseUrl. Next, we will set up the proper endpoints needed for signing users up and logging them in. We also need to override the tokenName with what our API serves, which in this case is id_token. Our API serves both id_token and access_token but we'll see how to handle the access_token later in the tutorial. Finally, we say that we want to redirect the users to the welcome view once they log in.

Application Routing Config

We'll now need to set up the application's routing configuration. Let's first set up the HTML that will require and load our nav bar and other views:

  <!-- client/src/app.html -->
  <template>
    <require from='./nav-bar'></require>

    <nav-bar router.bind="router"></nav-bar>

    <div class="container">
      <router-view></router-view>
    </div>

  </template>

Here, we are requiring the nav-bar and binding it to the router. We will serve our views from the <router-view> within our containing <div>.

// client/src/app.js

import 'bootstrap';
import 'bootstrap/css/bootstrap.css!';

import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
import HttpClientConfig from 'paulvanbladel/aureliauth/app.httpClient.config';
import AppRouterConfig from 'router-config';

// Using Aurelia's dependency injection, we inject Aurelia's router,
// the aurelia-auth http client config, and our own router config
// with the @inject decorator.
@inject(Router, HttpClientConfig, AppRouterConfig)

export class App {

  constructor(router, httpClientConfig, appRouterConfig) {

    this.router = router;

    // Client configuration provided by the aureliauth plugin
    this.httpClientConfig = httpClientConfig;

    // The application's configuration, including the
    // route definitions that we've declared in router-config.js
    this.appRouterConfig = appRouterConfig;
  };

  activate() {

    // Here, we run the configuration when the app loads.
    this.httpClientConfig.configure();
    this.appRouterConfig.configure();

  };
}

The HTTP configuration that aurelia-auth provides is what handles adding the JWT as a header if the user is authenticated. The httpClientConfig file has logic that checks for the existence of a token in localstorage and then adds an Authorization header with a value of Bearer <token> if one exists. The token will be sent for all HTTP requests to the API but will obviously only be needed for protected resources.

We can keep our routing logic within the main app.js file, as is done in many Aurelia projects, but in our case, we'll put this configuration in a separate file called router-config.js that we are injecting. Let's set up this routing configuration:

// client/src/router-config.js

import {AuthorizeStep} from 'paulvanbladel/aureliauth';
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';

// Using Aurelia's dependency injection, we inject Router
// with the @inject decorator
@inject(Router)

export default class {

  constructor(router) {
    this.router = router;
  };

  configure() {

    var appRouterConfig = function(config) {

      config.title = 'Random Quotes App';

      // Here, we hook into the authorize extensibility point
      // to add a route filter so that we can require authentication
      // on certain routes
      config.addPipelineStep('authorize', AuthorizeStep);

      // Here, we describe the routes we want along with information about them
      // such as which they are accessible at, which module they use, and whether
      // they should be placed in the navigation bar
      config.map([
          { route: ['','welcome'], name: 'welcome', moduleId: './welcome', nav: true, title:'Welcome' },
          { route: 'random-quote', name: 'random-quote', moduleId: './random-quote', nav: true, title:'Random Quote' },
          // The secret-quote route is the only one that the user needs to be logged in to see,  so we set auth: true
          { route: 'secret-quote', name: 'secret-quote', moduleId: './secret-quote', nav: true, title:'Super Secret Quote', auth: true },
          { route: 'signup', name: 'signup', moduleId: './signup', nav: false, title:'Signup', authRoute: true },
          { route: 'login', name: 'login', moduleId: './login', nav: false, title:'Login', authRoute: true },
          { route: 'logout', name: 'logout', moduleId: './logout', nav: false, title:'Logout', authRoute: true }
        ]);
      };

    // The router is configured with what we specify in the appRouterConfig
    this.router.configure(appRouterConfig);

  };
}

Aurelia gives us the ability to customize the navigation pipeline with some extensibility points, including an authorize route filter. Using this filter means we can specify which routes we would like authentication to be required for. Since our super-secret-quotes route needs to remain top secret until the user is logged in, we put auth: true in it. We hook into this filter by calling addPipelineStep, passing in the AuthorizeStep that is provided by the aurelia-auth plugin.

With the configuration out of the way, let's get to coding the actual routes and their views! We'll need to have files that take care of each route in place before the app will work so you can comment out the routes in router-config.js that aren't ready yet.

Setting up Routes and Views

Two files are required for each route in Aurelia--a JavaScript file for the view model logic and an HTML file for the view itself. Views are enclosed within <template> tags but are otherwise created with normal HTML that can make use of Aurelia's databinding.

The Nav Bar and Welcome Route

Let's start at the top and setup the navigation bar.

  <!-- nav-bar.html -->

  ...

  <ul class="nav navbar-nav">
    <li repeat.for="row of router.navigation | authFilter: isAuthenticated" class="${row.isActive ? 'active' : ''}">
      <a data-toggle="collapse" data-target="#bs-example-navbar-collapse-1.in" href.bind="row.href">${row.title}</a>
    </li>
  </ul>

  <ul if.bind="!isAuthenticated" class="nav navbar-nav navbar-right">
    <li><a href="/#/login">Login</a></li>
    <li><a href="/#/signup">Signup</a></li>
  </ul>

  <ul if.bind="isAuthenticated" class="nav navbar-nav navbar-right">
    <li><a href="/#/logout">Logout</a></li>
  </ul>

  ...

Notice here that we're running a filter on the repeated navigation items with authFilter: isAuthenticated. This allows us to hide any nav menu items that are to be protected if the user isn't authenticated, and this is how we will hide the super-secret-quote menu item when the user isn't logged in. We're also conditionally showing the Signup, Login, and Logout links. See the GitHub repo for the rest of the markup.

// client/src/nav-bar.js

import {bindable} from 'aurelia-framework';
import {inject} from 'aurelia-framework';
import {AuthService} from 'paulvanbladel/aureliauth';

@inject(AuthService)

export class NavBar {
  // User isn't authenticated by default
  _isAuthenticated = false;
  @bindable router = null;

  constructor(auth) {
    this.auth = auth;
  };

  // We can check if the user is authenticated
  // to conditionally hide or show nav bar items
  get isAuthenticated() {
    return this.auth.isAuthenticated();
  };
}

Here in the nav-bar.js file, we have a method that checks whether the user is logged in, which is what we hook into in the view.

The Aurelia seed comes with a welcome route, but in our case, we can trim it down to be simpler.

  <!-- client/src/welcome.html -->

  <template>
    <section>
      <h2>${heading}</h2>

      <div class="well">
        <h4>${info}</h4>
      </div>

    </section>
  </template>

The JavaScript becomes simpler as well.

// client/src/welcome.js

export class Welcome {

  heading = 'Welcome to the Random Quotes App!';
  info = 'You can get a random quote without logging in, but if you do log in you can get a super secret quote!';

}

Signup, Login, and Logout

Next, let's set up the signup, login, and logout routes.

Signup

  <!-- client/src/signup.html -->

  ...

  <form role="form" submit.delegate="signup()">
    <div class="form-group">
      <label for="email">Email</label>
      <input type="text" value.bind="email" class="form-control" id="email" placeholder="Email">
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" value.bind="password" class="form-control" id="password" placeholder="Password">
    </div>
    <button type="submit" class="btn btn-default">Signup</button>
  </form>
  <hr>
  <div class="alert alert-danger" if.bind="signupError">${signupError}</div>

  ...

In this view, we're providing two <input>s that take the user's email and password. We've also got an alert box at the bottom to show the user any errors that are returned. We'll need to set up the view models for these next.

// client/src/signup.js

import {inject} from 'aurelia-framework';
import {AuthService} from 'paulvanbladel/aureliauth';

// Using Aurelia's dependency injection, we inject the AuthService
// with the @inject decorator
@inject(AuthService)

export class Signup {

  heading = 'Sign Up';

  // These view models will be given values
  // from the signup form user input
  email = '';
  password = '';

  // Any signup errors will be reported by
  // giving this view model a value in the
  // catch block within the signup method
  signupError = '';

  constructor(auth) {
    this.auth = auth;
  };

  signup() {

    // Object to hold the view model values passed into the signup method
    var userInfo = { email: this.email, password: this.password }

    return this.auth.signup(userInfo)
    .then((response) => {
      localStorage.setItem('access_token', response.access_token);
      console.log("Signed Up!");
    })
    .catch(error => {
      this.signupError = error.response;
    });

  };
}

The signup() method uses aurelia-auth to send a POST request to the API, which either creates a new user or returns an error if there was a problem. During the process of creating a new user, if successful, both id_token and access_token are returned. The aurelia-auth plugin automatically stores the id_token in localStorage, while we manually store the access_token returned.

Note: The aurelia-auth plugin automatically stores just one token, thus the reason why we are storing the access_token manually.

Login

The login route is pretty similar. You'll just need to swap out submit.delegate="signup()" forsubmit.delegate="login()" and adjust the other pieces of markup appropriately.

The JavaScript for login looks similar as well, but this time, we are sending the POST request to the sessions/create endpoint:

// client/src/login.js

import {AuthService} from 'aurelia-auth';
import {inject} from 'aurelia-framework';
        
@inject(AuthService)

export class Login {

  heading = 'Log in';

  // User inputs will be bound to these view models
  // and when submitting the form for login
  email = '';
  password = '';

  // This view model will be given an error value
  // if anything goes wrong with the login
  loginError = '';

  constructor(auth) {
    this.auth = auth;
  };
        
  login() {
    return this.auth.login(this.email, this.password)     
    .then(response => { 
      localStorage.setItem('access_token', response.access_token);
      console.log("Login response: " + response);  
    })  
    .catch(error => {     
      this.loginError = error.response;  
    });     
  };  
}

Logout

The logout route essentially follows the same pattern using authService.logout() to remove the user's JWT from localstorage. See the repo for further detail.

With all this in place, we should now be able to sign up, log in, and log out users. Test it out to make sure that everything is running as expected. If everything is working properly, when the user logs in there will be two tokens set in localStorage as aurelia_id_token and access_token.

Aurelia localStorage Tokens

The Random Quote and Super-Secret Quote Routes

With signup, login, and logout in place, we now need to create the files for our quote routes. Let's first take care of the random-quote route.

  <!-- client/src/random-quote.html -->

  <template>
    <section class="col-sm-12">
        <h2>${heading}</h2>
        <div class="row">
          <div class="well">
            <h4>${randomQuote}</h4>
          </div>
        </div>
    </section>
  </template>

This view simply displays the heading and the text of the quote that we retrieve.

// client/src/random-quote.js

import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';

// Using Aurelia's dependency injection, we inject HttpClient
// with the @inject decorator to make HTTP requests
@inject(HttpClient)

export class RandomQuote {

  heading = 'Random Quote';

  // View model that will be populated with the
  // the random quote retrieved from the API and
  // displayed in the view
  randomQuote = '';

  constructor(http) {
    this.http = http;
  };

  activate() {
    return this.http.get('http://localhost:3001/api/random-quote')
    .then(response => {
      this.randomQuote = response.content;
    }).catch(error => {
      console.log('Error getting quote');
    });
  };
}

We want to fetch the quote when the route is hit, so within the activate() method, we are making a GET request to our random-quote endpoint, which is located at localhost:3001/api/random-quote. If we get a good response, we set the quote text onto randomQuote so that it can be accessed in the view.

The super secret-quote route is a little bit different. We are making a request to a different endpoint. We are also sending an Authorization header with a Bearer token, which is an access token in this case.

In case you are wondering why we are using an access_token, and not id_token, check out why you should use access tokens to secure an api.

For the view in secret-quote.html, make sure to change ${randomQuote} to ${secretQuote}.

// client/src/secret-quote.js

import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-http-client';

// Using Aurelia's dependency injection, we inject HttpClient
// with the @inject decorator to make HTTP requests
@inject(HttpClient)

export class SecretQuote {

  heading = 'Super Secret Quote';

  // View model that will be populated with the
  // the secret quote retrieved from the API and
  // displayed in the view
  secretQuote = '';

  constructor(http) {
    this.http = http.configure(x => {
      x.withHeader('Authorization', 'Bearer ' + localStorage.getItem('access_token'));
    });
  };

  activate() {
    return this.http.get('http://localhost:3001/api/protected/random-quote')
    .then(response => {
      this.secretQuote = response.content;
    }).catch(error => {
      console.log('Error getting quote');
    });
  };
}

If there is no valid access_token in localStorage, we won't be able to get to this route. If somehow we got to it, the request would fail because no JWT would be sent to the server.

Aside: Using Aurelia with Auth0

Auth0 issues JSON Web Tokens on every login for your users. This means that you can have a solid identity infrastructure, including single sign-on, user management, support for social (Facebook, Github, Twitter, etc.), enterprise (Active Directory, LDAP, SAML, etc.) and your own database of users with just a few lines of code.

You can use Lock to integrate Auth0 with Aurelia as well and avoid having to deal with authentication!

To start, sign up for your free Auth0 account if you haven't already. Then bring in the Auth0Lock script.

  <!-- Auth0 Lock script -->
  <script src="//cdn.auth0.com/js/lock-8.1.min.js"></script>

  <!-- Setting the right viewport -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />

Next, instantiate Lock with your Auth0 credentials.

// app.js

export class App {

  lock = new Auth0Lock('AUTH0_CLIENT_ID', 'AUTH0_DOMAIN');

  ...
}

To log a user in, create a login method that opens the Lock widget and saves the returned profile and JWT in local storage.

// app.js

login() {
  this.lock.show((err, profile, token) => {
    if(err) {
      console.log(err);
    }
    else {
      localStorage.setItem('profile', JSON.stringify(profile));
      localStorage.setItem('id_token', token);
      this.isAuthenticated = true;
    }
  });
}

Important API Security Note: If you want to use Auth0 authentication to authorize API requests, note that you'll need to use a different flow depending on your use case. Auth0 idToken should only be used on the client-side. Access tokens should be used to authorize APIs. You can read more about making API calls with Auth0 here.

To make authenticated HTTP calls, simply attach the user's JWT as an Authorization header. This can be done on a per request basis, or you can configure all HTTP calls to include the header.

// app.js

// Send the Authorization header with the JWT in a single HTTP call

getSecretThing() {
  this.http.fetch('/api/protected-route', {
    headers: {
      'Authorization': 'Bearer ' + localStorage.getItem('access_token')
    }
  })
  .then(response => response.json())
  .then(data => this.secretThing = data.text);
}
// Send the Authorization header in all HTTP calls

// app.js

constructor(http) {
  this.http = http;
  this.http.configure(config => {
    config.withDefaults({
      headers: {
        'Authorization': 'Bearer ' + localStorage.getItem('access_token')
      }
    });
  });
}

To log the user out, simply remove their profile and JWT from local storage.

// app.js

logout() {
  localStorage.removeItem('profile');
  localStorage.removeItem('id_token');
  localStorage.removeItem('access_token');
  this.isAuthenticated = false;
}

That's it! You now have authentication with Auth0 in your Aurelia app.

For more details, including how to protect certain route in your app, check out the Auth0 Aurelia docs. There you can also download a seed project to get started from scratch.

Wrapping Up

Protecting routes that we want to limit to logged-in users is actually quite simple with the aurelia-auth plugin because we can set the app up such that, if users aren't logged in, they can't see or reach protected routes. If, for whatever reason, users were able to get to a route they shouldn't be at and they don't have a valid JWT, any HTTP requests they make will be denied by the server. Properly authenticated users will have a valid JWT in localstorage, which will be sent along as a header with all the requests they make which will give them access to protected resources.

Aurelia is a great next-generation JavaScript framework with a growing user base. As we have seen in this tutorial, we can easily get users signed up and authenticated using the aurelia-auth plugin.