developers

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

Aug 5, 201518 min read

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()"
for
submit.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 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.