How to use ESA or ESAT to authenticate against custom provider

Here’s basically all of our authentication code, should be pretty much in line with what you want to do.

Install Ember Simple Auth

First install ember-simple-auth. Obvious first step. ember install ember-simple-auth

Create a callback route (we called ours ‘callback’)

This is the route your API will redirect the users browser to once authentication has been completed. From here the ESA implicit grant mixin will process your access token and then it can forward you on to the original desired URL, or to a default ‘routeAfterAuthentication’

ember g route <route-name>

Create authenticated route mixin

Next create a mixin in your project called ‘authenticated-route-mixin’ which extends the ESA authenticated-route-mixin with your authenticationRoute (and other) settings:

import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

import ENV from '../config/environment';

export default Ember.Mixin.create(AuthenticatedRouteMixin, {
  // NOTE: this used to be something we could set in config/environment.js but
  //       that usage was removed in ESA recently and now it must be set
  //       individually on all routes that extend AuthenticatedRouteMixin, so
  //       instead of duplicating that prop in every single one of our routes
  //       I am extending the ESA mixin with our own (of the same name) and
  //       adding the authenticationRoute property here.
  //
  //       (see https://github.com/simplabs/ember-simple-auth/pull/985)


  // these can either be static strings or computed properties (they are CPs
  // in the base ESA mixin which we're extending)

  authenticationRoute: 'callback', // use the route you generated above

  //routeAfterAuthentication: '',

  //routeIfAlreadyAuthenticated: '',
});

Extend the mixin you just created in your routes

In all your protected routes you should extend the mixin that you just created locally (which itself extends ESA’s) instead of extending ESA’s directly. This will allow you to set the authenticationRoute property once instead of having to do it on all of your routes.

e.g.:

import Ember from 'ember';
import AuthenticatedRouteMixin from "<project-name>/mixins/authenticated-route-mixin";

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  ...
});

Create authorizer

Create a file called <project-root>/authorizers/application.js and put the following in it (unless you want a different authorizer):

import OAuth2Bearer from 'ember-simple-auth/authorizers/oauth2-bearer';

export default OAuth2Bearer.extend();

Create an authenticator

Create an authenticator, ours is in <project-root>/authenticators/oauth2.js and extend the ESA implicit grant authenticator, and set your token endpoint:

import ENV from '../config/environment';
import OAuth2ImplicitGrant from 'ember-simple-auth/authenticators/oauth2-implicit-grant';

export default OAuth2ImplicitGrant.extend({/* any customization here, we have none */});

[optional] Define a session store

We just use the default adaptive store but if you want a custom one follow the README to define one

Add some magic to your callback route

Since you don’t want a landing page and all your routes will be protected you’ll want to add some magic to your callback route so you can use it on both ends of the authentication process. Our callback route looks like this, you may need to change a couple of the specifics of the original authentication URL to fit your backend:

import Ember from 'ember';
import CallbackRouteMixin from 'ember-simple-auth/mixins/oauth2-implicit-grant-callback-route-mixin';

import ENV from '../config/environment';

export default Ember.Route.extend(CallbackRouteMixin, {
  routeOnError: 'index',
  authenticator: 'authenticator:oauth2',

  generateAuthUrl: function(authHost, redirectHost, redirectHash) {
    let redirect_uri = redirectHost + encodeURIComponent("/#/callback");
    let client_id = ENV.APP.OAUTH_CLIENT_ID;
    let response_type = "token id_token"; // this is what we use, YMMV
    return `${authHost}/oauth/authorize?client_id=${client_id}&response_type=${response_type}&redirect_uri=${redirect_uri}&scope=openid&state=${redirectHash}`;
  },

  activate(){
    this._super();
    // if we don't have an access token, it means we're not authenticated and we should redirect the user to the API login page
    if(window.location.hash.indexOf('access_token') === -1) {
      let redirectHost = window.location.origin; // hostname of the URL we're on
      let redirectHash = encodeURIComponent(window.location.hash.slice(1).split('?')[0]); // our hash location aka our ember route
      if(redirectHash === "") {
        redirectHash = "/";
      }
      // generate an auth url which is our API server and where we want the API to redirect us back to
      let authUrl = this.generateAuthUrl(ENV.APP.OAUTH_HOST, redirectHost, redirectHash);
      // make the redirect
      window.location.replace(authUrl);
    } 
  },
});

Basically the way it works is:

  • a user will hit your app, but they are unauthenticated, so ESA realizes this and forwards the app to your authenticationRoute which you defined in your mixin above and is in this case the name of your callback route (again we just called it ‘callback’)
  • Once Ember gets here (the activate method of your callback route) it will see that there is no access token in the hash, so it will construct the authentication url and redirect to it, sending the user’s browser to your API login page with params specifying redirect_uri, etc
  • The user logs in, and once that happens the API forwards them back to the redirect_uri which you provided the API with on the way in, it includes the access_token in the hash params. Since you put your callback route in as the redirect_uri, you will be forwarded right back to your callback route, but this time you have the access_token in your hash params so ESA takes over and processes everything as a normal implicit grant flow

Anyway hope all this is helpful and again feel free to post questions here once you get that far

2 Likes