A currentUser service for Ember with JWT

By Matt Layman on January 31, 2017

Any Software as a Service will likely need the capability to authenticate a user and show information about that user. As an interesting design choice, Ember does not include authentication as a core feature. Instead, Ember developers must turn to the addon ecosystem. After some research, I chose Ember Simple Auth with Ember Simple Auth Token to use JSON Web Tokens (JWT) for College Conductor. JSON Web Tokens are an emerging standard that make secure exchanges (like authentication) possible. To provide user information throughout the application, I had to do some extra work to create an Ember service. I’m going to cover what I learned in the process.

Thankfully for me (and you), Ember Simple Auth has good documentation to cover a wide variety of uses. As a template, I used the Managing a Current User guide to direct the service that I needed. This guide lays out the structure required to extract information from the session service provided by Ember Simple Auth.

Generating a service to start is one command away:

$ ember generate service current-user

We can begin by looking at the code in the Ember Simple Auth guide.

import Ember from 'ember';

const { inject: { service }, isEmpty, RSVP } = Ember;

export default Ember.Service.extend({
  session: service('session'),
  store: service(),

  load() {
    let userId = this.get('session.data.authenticated.user_id');
    if (!isEmpty(userId)) {
      return this.get('store').findRecord('user', userId).then(
        (user) => {
          this.set('user', user);
        });
    } else {
      return Ember.RSVP.resolve();
    }
  }
});

This service is almost exactly what we need but there is a problem. When using JWT, the data stored in the session is an encoded token. The service needs to get the user ID and it’s not easily accessible. That leads to code like:

import Ember from 'ember';

const { inject: { service }, isEmpty, RSVP } = Ember;

export default Ember.Service.extend({
  session: service(),
  store: service(),

  load() {
    const token = this.get('session.data.authenticated.token');
    if (!isEmpty(token)) {
      const userId = this.getUserIdFromToken(token);
      return this.get('store').find('user', userId).then((user) => {
        this.set('user', user);
      });
    } else {
      return RSVP.resolve();
    }
  },

  getUserIdFromToken(token) {
    // What goes here?
    return 42;
  }
});

As I explored the source code, I discovered that the session did not store the decoded token data. I had to find a way to decode the token so I could extract the user_id that my API provided. The JWT authenticator in Ember Simple Auth Token included what I needed. Ember Simple Auth does not expose a method to get the authenticator (please prove me wrong!) so I opted to create an instance myself and invoke its getTokenData method.

The result looks like:

import JWT from 'ember-simple-auth-token/authenticators/jwt';

<snip>

  getUserIdFromToken(token) {
    const jwt = new JWT();
    const tokenData = jwt.getTokenData(token);
    return tokenData['user_id'];
  }

If I reflect on what I had to do, I think it is safe to state that the final change was not too difficult. The part that I find interesting is that I had to be comfortable digging through the source code of Ember Simple Auth Token.

Reading other source code is a needed developer skill.

Last week, I wrote about software abstractions and the benefits of boundaries. The boundaries provided by abstractions are extremely useful, but we should be comfortable breaking through them when it seems like there are no options. Reading the source code of third party addons, extensions, plugins, and the like is a useful tool that can often make quick work of a tricky problem.

If you want to chat about this with me, I'm @mblayman on Twitter.



Matt Layman

Matt is the lead software engineer at Storybird.

Always eager to talk about Python and other technology topics, Matt organizes Python Frederick in Frederick, Maryland (NW of Washington D.C.) and seeks to grow software skills for people in his community.