close icon
Ember

EmberJS Authentication Tutorial

Learn how to quickly build ambitious apps with EmberJS 2 and add authentication the right way.

May 25, 2017

Visit Build and Authenticate an EmberJS 3 Application to read our most up-to-date tutorial.
This tutorial has been deprecated.

TL;DR: EmberJS is a JavaScript framework for building ambitious web applications. It's a framework that was built for productivity and designed with developer ergonomics in mind. Currently, EmberJS has over 15,000 stars on GitHub. In this tutorial, I'll show you how easy it is to build a web application with EmberJS 2 and add authentication to it. Check out the repo to get the code.


EmberJS was developed by Yehuda Katz. It was initially released in December 2011. EmberJS was also formerly known as SproutCore MVC framework. New applications now run on EmberJS 2 which was released in August, 2015. EmberJS 2.0 introduced new APIs, and removed deprecated ones from Ember 1. The goal of Ember 2 is to remove badly designed and unecessarily complicated code from Ember 1. And apps that run on Ember 1.13 without any deprecation warnings should run without issues on Ember 2.0. Currently, many popular products use EmberJS to build their user interfaces. Such platforms include LinkedIn, Yahoo, Zendesk, Square, PlayStation Now, Apple Music, Heroku Dashboard, Twitch, Discourse, IndieHackers and more. There is a comprehensive list of projects using Emberjs on builtwithember.io. EmberJS documentation is very detailed, and there is a vibrant community of users.

Understanding Core Concepts in EmberJS

If you have experience with frameworks like VueJS, Angular and React, then you'll understand how EmberJS works in a split second. Developers coming from the jQuery world might find it difficult to comprehend at first glance. But if you are familiar with frameworks like Laravel and Rails, then you'll discover a pattern that'll make fall in love with EmberJS.

I'll give a basic overview of these concepts to nourish your understanding of EmberJS. They are:

  • Routing
  • Templates
  • Models
  • Components
  • Controllers

Routing

Let's take a good look at how a typical EmberJS app works. Our fictitious app is a Student Management System and the URL is https://studember.ng. One of the URLs in this app is https://studember.ng/students. This route simply returns the details of all the students registered on this app. Now, check out what happens when the user loads the app for the first time.

Ember router maps the URL to a route handler. The route handler then renders a template and loads a model that is available to the template.

import Ember from 'ember';
import config from './config/environment';

const Router = Ember.Router.extend({
  location: config.locationType,
  rootURL: config.rootURL
});

Router.map(function() {
  this.route('students');
});

export default Router;

router

import Ember from 'ember';

export default Ember.Route.extend({
    model() {
        return [
            'David Ajimobi',
            'Olorigbeske Ojuyobo',
            'Orieja Michael'
        ]
    }
});

Route Handler

You can easily create a route using the Ember CLI's generate command like so:

ember generate route route-name

This creates a route file at app/routes/route-name.js, a template for the route at app/templates/route-name.hbs, and a unit test file at tests/unit/routes/route-name-test.js. It also adds the route to the router.

Templates

Templates are used to organize the HTML layout of the application. By default, EmberJS uses Handlebars templates. Templates can display properties provided to them from a controller or a component. The screen rendered to the user is composed of handlebars templates. A typical example is this:

<h3> {{title}} </h3>
<ul>
    {{#each people as |person| }}
        <li>{{person}}</li>
    {{/each}}
</ul>

template.hbs

The template extension is .hbs.

Model

Models are objects that represent the underlying data that your application presents to the user. The structure and scope of your app will determine the types and number of models that will present in it.

A typical example is this:

Our student management app might have a Student model to represent a particular student. Models are also used to persist data. Typically, most models fetch and persist data to a store. The store could be a database on a server or simply a JSON file.

import DS from 'ember-data';

export default DS.Model.extend({
  first_name: DS.attr(),
  last_name: DS.attr(),
  city: DS.attr(),
  age: DS.attr(),
});

app/models/student.js

Ember comes with a data management library called Ember Data to help deal with persistent application data. The library requires you to define the structure of the data you wish to provide to your application by extending DS.Model. At first, using Ember Data may feel different than the way you're used to writing JavaScript applications. Many developers are familiar with using AJAX to fetch raw JSON data from an endpoint, which may appear easy at first. Over time, however, complexity leaks out into your application code, making it hard to maintain. Ember Data helps you manage your models in a simple way as your application grows.

Ember Data gives you a single store that is the central repository of models in your application. Components and routes can ask the store for models, and the store is responsible for knowing how to fetch them.

You can easily create a model using the Ember CLI's generate command like so:

ember generate model student

This creates a model at app/models/student.js file:

import DS from 'ember-data';

export default DS.Model.extend({
});

Components

Ember Components consist basically of two parts: a Handlebars template, and a JavaScript file that defines the component's behavior.

Components must have at least one dash in their name, e.g active-list. This helps Ember differentiate it from native HTML tags. Components control how the user interface behaves. Components are represented in the view with the curly brace rather than the angle tag like this:

{{active-list}}

Ember provides some methods that are triggered at various points from creating a component up until the component is destroyed. This is called the Component's Lifecycle. You can declare methods to hook into the component's lifecycle to control the behaviour of components in your app.

On Initial Render, we have:

  • init
  • didReceiveAttrs
  • willRender
  • didInsertElement
  • didRender

On Re-render, we have:

  • didUpdateAttrs
  • didReceiveAttrs
  • willUpdate
  • willRender
  • didUpdate
  • didRender

On Component Destroy, we have:

  • willDestroyElement
  • willClearRender
  • didDestroyElement

A typical example of a component is this:

import Ember from 'ember';

export default Ember.Component.extend({
  init() {
    this._super(...arguments);
    this.errors = [];
  },

  didUpdateAttrs() {
    this._super(...arguments);
    this.set('errors', []);
  },

  actions: {
    required(event) {
      if (!event.target.value) {
        this.get('errors').pushObject({ message: `${event.target.name} is required`});
      }
    }
  }
});

You can easily create a component using the Ember CLI's generate command like so:

ember generate component student-list

This creates a component file at app/components/student-list.js, a template for the component at app/templates/components/student-list.hbs, and an integration test file at tests/integration/components/student-list-test.js.

Controllers

Ember Controllers are routable objects meant to decorate a model with display logic. They sit between the template and model to deal with logic and properties that do not belong to the view or the model.

When you have a property that needs to be in the template but doesn't exist in the model, you can place it the controller:

import Ember from 'ember';

export default Ember.Controller.extend({
  canDelete: true
});

This property can now be accessed in the template:

{{#if canDelete}}
// Go ahead and delete the student
{{/if}}

The controller can also be used to make model data more readable to the user. An example is returning the fullname of the user:

import Ember from 'ember';

export default Ember.Controller.extend({
  getFullName() {
    return `${this.get('model.firstName')} - ${this.get('model.lastName')}`
  }
});

We can just call getFullName in the template:

  <span>{{ getFullName }} is the senior prefect.</span>

You can easily create a controller using the Ember CLI's generate command like so:

ember generate controller students

This creates a controller file at app/controllers/students.js, and a unit test file at tests/unit/controllers/students-test.js.

Next, let's build an application with Emberjs 2.

Our App: Whistle Blower

Whistle Blower

The app we will build today is called Whistle Blower. A Whistle Blower is a person who exposes any kind of information or activity that is deemed illegal, unethical, or not correct within an organization that is either private or public. The Whistle Blower app does the following:

  • It gives information about whistle blowing activities in your region.
  • It's a small community of whistle blowers.
  • A guest user on the Whistle Blower app will only have acess to basic information about the whistle blowing activities on the landing page.
  • An authenticated user will have access to whistle blowers and their profiles.
  • An authenticated user will have access to whistle blower meetups/gatherings.

Build The Back-End

Let's build an API for our app. We'll quickly build the API with Node.js. The API is simple. This is what we need:

  • An endpoint to serve the latest whistle blowing activities around the world - /api/activities.
  • An endpoint to serve whistle blowers and their profiles - /api/whistleblowers.
  • An endpoint to serve whistle blower meetups - /api/meetups.
  • Securing the endpoint that serves whistle blowers profiles and meetups, so that it can only be accessed by registered users.

Go ahead and fetch the Node.js backend from GitHub.

Note: We'll be securing the backend with Auth0, so make sure you have an account already or sign up for one.

Your server.js should look like this:

'use strict';

const express = require('express');
const app = express();
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());

const authCheck = jwt({
  secret: jwks.expressJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: "https://{YOUR-AUTH0-DOMAIN}/.well-known/jwks.json"
    }),
    // This is the identifier we set when we created the API
    audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
    issuer: "{YOUR-AUTH0-DOMAIN}",
    algorithms: ['RS256']
});

app.get('/api/activities', (req, res) => {
  let whistleBlowerActivities = [
    // An array of whistleblowing activities
  ];

  res.json(whistleBlowerActivities);
})

app.get('/api/whistleblowers', (req,res) => {
  let whistleBlowers = [
    // An aray of whistle blowers
  ];

  res.json(whistleBlowers);
})

app.get('/api/meetups', (req,res) => {
  let meetups = [
    // An array of meetups
  ];

  res.json(meetups);
})

app.listen(3333);
console.log('Listening on localhost:3333');

Check out the full server.js file here.

Note: YOUR-AUTH0-DOMAIN should be replaced with your Auth0 domain.

server.js

Your package.json file should look like this:

{
  "name": "whistleblower",
  "version": "0.0.1",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "nodemon server.js"
  },
  "author": "Auth0",
  "license": "MIT",
  "dependencies": {
    "body-parser": "^1.15.2",
    "cors": "^2.8.1",
    "express": "^4.14.0",
    "express-jwt": "^3.4.0",
    "jwks-rsa": "^1.1.1"
  }
}

Note: Make sure you have nodemon installed globally.

package.json

Once you have cloned the project, run an npm install, then use postman to serve your routes like so:

API serving meetups API serving meetups

API serving whistle blowers API serving whistle blowers

API serving whistle blowing activities API serving whistle blowing activities

The public whistle blowing activities endpoint should be http://localhost:3333/api/activities.

The private meetup endpoint should be http://localhost:3333/api/meetups.

The private whistle blower endpoint should be http://localhost:3333/api/whistleblowers.

Don't worry about the middleware in charge of securing our endpoint for now. We'll deal with that later. Now, let's build our frontend with EmberJS 2.

Build The Front-End With EmberJS 2

EmberJS has a very nice tool for scaffolding your apps. It's called the ember-cli. It's being maintained by the Ember team.

Go ahead and install the ember-cli tool globally like so:

npm install -g ember-cli

After installing globally, go ahead and scaffold a new EmberJS 2 app like so:

ember new whistleblower

Provisioning a new ember app

Move into the new directory, whistleblower and run ember serve to start up your app.

Default Page

Let's check out the structure of our newly scaffolded app.

Scaffolded App

whistleblower/
  app/ - All the controllers, components, routes and templates reside here
  config/ - All environment config files are here
  node_modules/ - All the packages required for the emberjs app resides here
  public/
  tests/ - All the tests file resides here
  vendor/
  .editorconfig
  .ember-cli
  .eslintrc.js
  .gitignore
  .travis.yml
  .watchmanconfig
  ember-cli-build.js
  package.json - File that contains the names of all the packages residing in node_modules folder
  README.md
  testem.js

Note: We are not writing any tests for this application. It's out of the scope of this tutorial.

We need to remove the default page content that ember presents in our app. Open app/templates/application.hbs file and remove this:

{!-- The following component displays Ember's default welcome message. --}}
{{welcome-page}}
{{!-- Feel free to remove this! --}}

Now, our app will show a blank screen. Sweet! let's get started.

Style with Bootstrap

Go ahead and open the app/index.html file. Here, we will add the link to the bootstrap css and js file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Whistleblower</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    {{content-for "head"}}
    <link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
    <link rel="stylesheet" href="{{rootURL}}assets/whistleblower.css">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
    {{content-for "head-footer"}}
  </head>
  <body>
    {{content-for "body"}}
    <script src="{{rootURL}}assets/vendor.js"></script>
    <script src="{{rootURL}}assets/whistleblower.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js">
    {{content-for "body-footer"}}
  </body>
</html>

Ember already provides a directory and a stylesheet file for your custom css. So, open app/styles/app.css and add the css here.

That's all about our styling. Next, let's create our routes.

Create the Routes

We need users to be able to access a URL:

  • That provides details about whistle blower meetups.
  • That provides details about whistle blowers.

We also need a callback URL. I'll tell you why we need that later in the tutorial. Let's create these routes ASAP. The ember-cli provides generator commands that makes this easy. So go ahead and run the following commands in your terminal:

ember generate route whistle-blowers
ember generate route meetups
ember generate route callback

The route files are generated together with their respective template files. EmberJS route handler renders a template when a route is invoked.

Building the Nav Component

Let's build the Nav Component. This component will be shared amongst all the pages. Generate the component using the ember-cli:

ember generate component app-nav

This command generates a component and a template for the app nav. Open up app/templates/components/app-nav.hbs and add this to it:

<nav class="navbar navbar-default">
    <div class="navbar-header">
       Whistle Blower
    </div>
    <ul class="nav navbar-nav navbar-right">
      <li>
          <button class="btn btn-danger log">Log out</button>
          <button class="btn btn-info log">Log In</button>
      </li>
    </ul>
</nav>

Now, next we need to create an utility file for authentication and fetching API data for our routes from the backend. Well, Ember allows us to create utilities, but this is better suited for services. An Ember service is a long lived Ember object that can be made available in different parts of your application. This is exactly what we need.

Creating Services

We need to create two services, the auth and API service. The former for everything related to user authentication and the latter for fetching API data from our server. Go ahead and create both services using the ember-cli:

ember generate service auth
ember generate service whistleblowerapi

app/services/auth.js and app/services/whistleblowerapi.js will be created. Now, open the auth service and add this to it:

app/services/auth.js

import Ember from 'ember';
import decode from 'npm:jwt-decode';
import auth0 from 'npm:auth0-js';
const ID_TOKEN_KEY = 'id_token';
const ACCESS_TOKEN_KEY = 'access_token';

const CLIENT_ID = '{AUTH0_CLIENT_ID}';
const CLIENT_DOMAIN = '{AUTH0_DOMAIN}';
const REDIRECT = '{CALLBACK_URL}';
const SCOPE = '{SCOPE}';
const AUDIENCE = '{API IDENTIFIER}';

export default Ember.Service.extend({

    auth: new auth0.WebAuth({
        clientID: CLIENT_ID,
        domain: CLIENT_DOMAIN
    }),

    login() {
        this.get('auth').authorize({
            responseType: 'token id_token',
            redirectUri: REDIRECT,
            audience: AUDIENCE,
            scope: SCOPE
        });
    },

    logout() {
        this.clearIdToken();
        this.clearAccessToken();
        window.location.href = "/";
    },

    getIdToken() {
        return localStorage.getItem(ID_TOKEN_KEY);
    },

    getAccessToken() {
        return localStorage.getItem(ACCESS_TOKEN_KEY);
    },

    clearIdToken() {
        localStorage.removeItem(ID_TOKEN_KEY);
    },

    clearAccessToken() {
        localStorage.removeItem(ACCESS_TOKEN_KEY);
    },

    // Helper function that will allow us to extract the access_token and id_token
    getParameterByName(name) {
        let match = RegExp('[#&]' + name + '=([^&]*)').exec(window.location.hash);
        return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
    },

    // Get and store access_token in local storage
    setAccessToken() {
        let accessToken = this.getParameterByName('access_token');
        localStorage.setItem(ACCESS_TOKEN_KEY, accessToken);
    },

    // Get and store id_token in local storage
    setIdToken() {
        let idToken = this.getParameterByName('id_token');
        localStorage.setItem(ID_TOKEN_KEY, idToken);
    },

    isLoggedIn() {
        const idToken = this.getIdToken();
        return !!idToken && !this.isTokenExpired(idToken);
    },

    getTokenExpirationDate(encodedToken) {
        const token = decode(encodedToken);
        if (!token.exp) { return null; }

        const date = new Date(0);
        date.setUTCSeconds(token.exp);

        return date;
    },

    isTokenExpired(token) {
        const expirationDate = this.getTokenExpirationDate(token);
        return expirationDate < new Date();
    }
});

Go ahead and install the auth0-js and jwt-decode packages from the terminal:

npm install auth0-js jwt-decode --save

Our auth service contain different functions for authenticating using auth0 hosted lock,saving/extracting tokens, checking expiry date and checking if a user is logged in or not.

Note: You can fetch a property in a service using the this.get('<name-of-property>') syntax.

Now, you might have noticed that we are importing them, using this syntax import module from npm:package. It turns out that CommonJS(Node) module doesn't play nice with ES6 import statements in Ember. It throws an error indicating that the module can't be found. As usual, we got a work around. To get our NPM CommonJS version of our node modules to work with our ES6 import, all we have to do is:

ember install ember-browserify

and append npm to the module name like we did in the code snippet for the auth service above.

Open the whistleblower API service and add this to it:

app/services/whistleblowerapi.js

import Ember from 'ember';
import axios from 'npm:axios';

const ACCESS_TOKEN_KEY = 'access_token';
const BASE_URL = 'http://localhost:3333';

export default Ember.Service.extend({

    getMeetups() {
        const url = `${BASE_URL}/api/meetups`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },

    getWhistleBlowers() {
        const url = `${BASE_URL}/api/whistleblowers`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },

    getActivities() {
        const url = `${BASE_URL}/api/activities`;
        return axios.get(url).then(response => response.data);
    },

    getAccessToken() {
        return localStorage.getItem(ACCESS_TOKEN_KEY);
    }
});

Install the axios module via your terminal:

npm install axios --save

Here, we fetched the meetups, whistleblowers and activities from the API. Ember already provides jQuery by default. So, an alternative is to use Ember.$.get(url) instead of axios. I personally love using axios, hence the reason I chose to use it here.

Ember Services are injectable. You can inject them into different parts of your application as the need arises.

Build the Routes

We created our routes earlier. Now, we need to pass data to the templates of these routes. Once a user hits a URL, they should be able to get data presented to them.

Ember provides a model method in routes that allows us to fetch data and pass it down to the route template. So, we'll add the model method into our routes, inject the API service and call the service methods to provide data to the model hook so that it can be passed down to the templates.

Open app/routes/meetups.js and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getMeetups();
    }
});

app/routes/meetups.js

Open app/routes/whistle-blowers.js and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getMeetups();
    }
});

app/routes/whistle-blowers.js

Now, we need to create a route for our index page, which is the landing page.

ember generate route index

Open app/routes/index and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    api: Ember.inject.service('whistleblowerapi'),

    model() {
        return this.get('api').getActivities();
    }
});

app/routes/index.js

Next, we need to display data in their respective templates.

Bring Templates to Life

Open app/templates/index.hbs and add this:

{{app-nav}}
<div class="container">
    <h3 class="text-center">Whistle Blowing Updates From Across The World</h3>
</div>
<br/>
{{#each model as |update|}}
    <div class="col-sm-6">
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title"> {{ update.title }} </h3>
        </div>
        <div class="panel-body">
          <p><span class="badge alert-danger"> Location: </span><strong> {{ update.location }} </strong></p>
        </div>
      </div>
    </div>
{{/each}}

We imported the app-nav component into this template to provide navigation menu for our app.

We also looped through the model data coming from the index route using the #each helper method.

Open app/templates/meetups.hbs and add this:

{{app-nav}}
<div class="container">
    <h3 class="text-center">Whistle Blower Meetups Across The World </h3>
</div>
<br/>
{{#each model as |meetup|}}
    <div class="col-sm-6">
      <div class="panel panel-default">
        <div class="panel-heading">
          <h3 class="panel-title"> {{ meetup.name }} </h3>
        </div>
        <div class="panel-body">
          <p><span class="badge alert-danger"> Location: </span><strong> {{ meetup.date }} </strong></p>
        </div>
      </div>
    </div>
{{/each}}
{{outlet}}

Open app/templates/whistle-blowers.hbs and add this:

{{app-nav}}
<div class="container">
    <h3 class="text-center">Whistle Blowers Across The World </h3>
</div>
<br/>
{{#each model as |whistleblower|}}
    <div class="col-lg-2 col-sm-4">
        <div class="card hovercard">
            <div class="cardheader">
            </div>
            <div class="avatar">
                <img alt="" src="{{whistleblower.avatar}}"/>
            </div>
            <div class="info">
                <div class="title">
                    <a target="_blank" href="http://scripteden.com/">{{ whistleblower.name }}</a>
                </div>
                <div class="desc">
                    <p><strong>{{whistleblower.level}}</strong></p></div>
                <div class="desc">
                    <p><span class="badge alert-danger"> Uncovered Spoils: </span> {{whistleblower.uncoveredSpoils}} </p></div>
            </div>
        </div>
    </div>
{{/each}}
{{outlet}}

Go to your browser and check out all the routes. They should be displaying the right data:

Index page Landing page

Meetups route Meetups Route

Whistleblowers route Whistleblowers route

Next, let's add authentication to our app.

Adding Authentication to Your EmberJS 2 App

The majority of the apps we use on a daily basis have a means of authenticating users. I'll show you how to easily add authentication to our Emberjs 2 application. We'll use Auth0 as our authentication service.

Auth0 allows us to issue JSON Web Tokens (JWTs). If you don't already have an Auth0 account, sign up for a free one now.

Auth0 provides the simplest and easiest to use user interface tools to help administrators manage user identities including password resets, creating and provisioning, blocking and deleting users.

Login to your Auth0 management dashboard and let's create a new API client. If you don't already have the APIs menu item, you can enable it by going to your Account Settings and in the Advanced tab, scroll down until you see Enable APIs Section and flip the switch.

From here, click on the APIs menu item and then the Create API button. You will need to give your API a name and an identifier. The name can be anything you choose, so make it as descriptive as you want. The identifier will be used to identify your API, this field cannot be changed once set. For our example, I'll name the API Startup Battle API and for the identifier I'll set it as http://whistleblower.com. We'll leave the signing algorithm as RS256 and click on the Create API button.

Creating the Whistle Blower API Creating the Whistle Blower API

Next, let's define some scopes for our API. Scopes allow us to manage access to our API. We can define as few or as many scopes as we want. For our simple example, we'll just create a single scope that will grant users full access to the API.

Locate scopes bar Locate Scopes bar

Adding Scope to API Adding scope

Secure The Node API

We need to secure the API so that the meetups and whistle blowers endpoints will only be accessible to authenticated users. We can secure it easily with Auth0.

Open up your server.js file and add the authCheck middleware to the private battles endpoint like so:

app.get('/api/meetups', authCheck, (req,res) => {
  let meetups = [
    // Array of meetups
  ];

  res.json(meetups);
})

app.get('/api/whistleblowers', authCheck, (req,res) => {
  let whistleBlowers = [
    // Array of whistle blowers
  ];

  res.json(whistleBlowers);
})

app.listen(3333);
console.log('Listening on localhost:3333');

Try accessing the http://localhost:3333/api/meetups endpoint again from Postman. You should be denied access like so:

Unauthorized Access Unauthorized Access

Next, let's add authentication to our front-end.

Adding Authentication to our EmberJS 2 Front-end

We created an auth service earlier. Open up app/services/auth.js again.

In the code present in this file, we are using an hosted version of Auth0 Lock in the login method. We also passed in our credentials.

The auth0 node module called the Auth0's authorize endpoint. With all the details we passed to the method, our client app will be validated and authorized to perform authentication. You can learn more about the specific values that can be passed to the authorize method here.

The parameters that you do not have yet are the {AUTH0-CLIENT-ID} and the {CALLBACK-URL}. When you created your API, Auth0 also created a test client which you can use. Additionally, you can use any existing Auth0 client found in Clients section of your management dashboard.

Check the Test panel of your API from the dashboard. You'll see the test client like so:

Startup Client WhistleBlower API Client

Now, go to the clients area and check for the test client. Open the client and change the Client Typefrom Non Interactive Client to Single Page Application.

Non interactive clients are meant to be used in machine to machine interactions. We are using an SPA to interact with the API so the client should be an SPA client. Check out Implicit Grant and client credentials exchange for more information.

Copy the CLIENT ID from the dashboard and replace it with the value of the CLIENT-ID constant variable in your code. Replace your callback url with http://localhost:4200/callback. Replace your client domain, scope and audience values with the domain from your Auth0 dashboard, scope from your API and API identifier respectively.

We checked whether the token has expired via the getTokenExpirationDate and isTokenExpired methods. The isLoggedIn method returns true or false based on the presence and validity of a user id_token.

Let's go update the app-nav component to hide/show the login and logout buttons based on the user's authentication status.

Now, your app-nav component template file should look like this:

<nav class="navbar navbar-default">
    <div class="navbar-header">
      {{#link-to 'index' class="navbar-brand"}} Whistle Blower {{/link-to}}
    </div>
    <ul class="nav navbar-nav">
        <li>
            {{#if loggedIn }}
                {{#link-to 'meetups' class="navbar-brand"}} Meetups {{/link-to}}
                {{#link-to 'whistle-blowers' class="navbar-brand"}} Whistle Blowers {{/link-to}}
            {{/if}}
        </li>
    </ul>
    <ul class="nav navbar-nav navbar-right">
      <li>
        {{#if loggedIn }}
            <button {{ action "logout" }} class="btn btn-danger log">Log out</button>
        {{else}}
            <button {{ action "login" }} class="btn btn-info log">Log In</button>
        {{/if}}
      </li>
    </ul>
</nav>

app/templates/components/app-nav.hbs

Now, we used some handlebars helpers in the code above such as #if for conditional operations, #link-to for linking to a route.

We have a loggedIn variable that is set as a flag for alternating the display of the login and logout buttons. Now, where did that variable come from?

Open the component file responsible for the app-nav template and add this:

import Ember from 'ember';

export default Ember.Component.extend({
    auth: Ember.inject.service('auth'),

    init() {
        this._super(...arguments);
        this.set('loggedIn', this.get('auth').isLoggedIn());
    },

    actions: {
        login() {
            this.get('auth').login();
        },
        logout() {
            this.get('auth').logout();
        }
    }
});

app/components/app-nav.js

We injected the auth service. Then we called the init method which is a component hook that is called whenever a component is been initialized. In the init method, we set a variable loggedIn with the value of the user's authentication status gotten from the auth service.

this._super(..arguments) has to be called to override the init method that ships with Ember components.

Next, we defined the actions. In Ember, components use actions to communicate events and changes. The methods defined in the actions object get called when the user needs to trigger an action on the UI either via buttons or any form field elements.

In our app-nav template, we have the logout and login buttons that trigger some actions:

{{#if loggedIn }}
  <button {{ action "logout" }} class="btn btn-danger log">Log out</button>
{{else}}
  <button {{ action "login" }} class="btn btn-info log">Log In</button>
{{/if}}

Activate the Callback Route

We created a callback route earlier. This route will be called when a redirect is made from Auth0 after a successful authentication. The route will process the redirect and store the access_token and id_token coming from Auth0. Open app/routes/callback.js and add this:

import Ember from 'ember';

export default Ember.Route.extend({

    auth: Ember.inject.service('auth'),

    beforeModel() {
        this.get('auth').setAccessToken();
        this.get('auth').setIdToken();
        this.transitionTo('/');
    },
});

app/routes/callback.js

Once a user is authenticated, Auth0 will redirect back to our application and call the /callback route. Auth0 will also append the id_token as well as the access_token to this request, and our callback route will make sure to properly process and store those tokens in localStorage. If all is well, meaning we recieved an id_token, access_token, we will be redirected back to the / page and will be in a logged in state.

Ember Routes provide a beforeModel hook that is called before the model is initialized. It can be used to conditionally prevent access to a route. It can also be used to perfom some actions like a middleware.

Add some values to Auth0 Dashboard

Just before you try to log in or sign up, head over to your Auth0 dashboard and add http://localhost:4200/callback to the Allowed Callback URLs and http://localhost:4200 to Allowed Origins (CORS) of your client.

Secure Meetups and WhistleBlowers Routes

We need to ensure that no one can go to the browser and just type /meetups and whistle-blowers to access the meetups and whistleblowers routes respectively.

Open up app/routes/meetups.js. Modify it to have the beforeModel hook and inject the auth service:

import Ember from 'ember';

export default Ember.Route.extend({

    auth: Ember.inject.service('auth'),

    api: Ember.inject.service('whistleblowerapi'),

    beforeModel() {
        if(!this.get('auth').isLoggedIn()) {
            this.transitionTo('/');
        }
    },

    model() {
        return this.get('api').getMeetups();
    }
});

app/routes/meetups.js

Open up app/routes/whistle-blowers.js. Modify it to also have the beforeModel hook and inject the auth service:

import Ember from 'ember';

export default Ember.Route.extend({

    auth: Ember.inject.service('auth'),

    api: Ember.inject.service('whistleblowerapi'),

    beforeModel() {
        if(!this.get('auth').isLoggedIn()) {
            this.transitionTo('/');
        }
    },

    model() {
        return this.get('api').getWhistleBlowers();
    }
});

app/routes/whistle-blowers.js

In both routes, just before the model initializes, we check if the user is logged in. If the user has not been authenticated, we redirect the user back to the landing page.

Now, try to log in.

Lock Login Widget Lock Login Widget

For the first time, the user will be shown a user consent dialog that will show the scopes available. Once a user authorizes, it goes ahead to login the user and give him access based on the scopes.

User consent dialog User presented with an option to authorize

Note: Since we are using localhost for our domain, once a user logs in the first time, subsequent logins will not need a user consent authorization dialog. This consent dialog will not be displayed if you are using a non-localhost domain, and the client is a first-party client.

Logged In and Athorized to see the private meetups page Logged In and authorized see the private meetups page

Logged In and Athorized to see the private whistleblowers page Logged In and authorized to see the private whistleblowers page

We have successfully logged in and can access the content of both private routes. We passed an option to send an Authorization header with a Bearer access_token along with the GET request in our API service. The request sends the JWT to the secured backend. The backend verifies it. If it is valid, the user is granted access to the resources.

...
  getMeetups() {
        const url = `${BASE_URL}/api/meetups`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },

    getWhistleBlowers() {
        const url = `${BASE_URL}/api/whistleblowers`;
        return axios.get(url, { headers: { Authorization: `Bearer ${this.getAccessToken()}` }}).then(response => response.data);
    },
...

What happens if we don't send an access_token? Go ahead and remove the Authorization header. Make a plain get request that doesn't send any JWT to the backend.

Aha, we get a blank page. The Chrome Dev tools screaming at us like:

Unauthorized 401 Unauthorized

Important API Security Note: Anytime you intend using Auth0 authentication to authorize API requests, note that you'll need to use a different flow depending on your use case. Auth0 id_token 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.

Conclusion

You have just successfully built an EmberJS 2 app and added authentication to it. To be honest, Ember has a steep learning curve. It takes a while to navigate where to find different files and put certain logic but once you get a hang of it, then it becomes a Saber for architecting and building ambitious web applications.

In addition, Auth0 can help secure your EmberJS apps with more than just username-password authentication. It provides features like multifactor auth, breached password detection, anomaly detection, enterprise federation, single sign on (SSO), and more. Sign up today so you can focus on building features unique to your app.

Auth0 offers a generous free tier to get started with modern authentication.

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon