Instantly share code, notes, and snippets.

@pauln /
Last active Jul 31, 2018

What would you like to do?
ember-simple-auth oauth2 implicit grant authenticator

oauth2 Implicit Grant authenticator for ember-simple-auth

This is a sample ember-simple-auth authenticator implementation for the oauth2 Implicit Grant which implements "silent reauthentication" (fetching a new token from the IDP via the prompt=none flow). It also uses ember-master-tab to run the refresh process in only a single tab (if the application is open in multiple tabs); at time of writing, it's necessary to use the master branch rather than the version published to npm as it makes use of a recent change to try to recover from the master tab crashing (as opposed to being closed cleanly).

This implementation also expects the token to be a JWT; you may need to adjust the token-related parts if you're not using JWTs.

So how do I use this?

This gist is not a fully developed, drop-in, ready-to-use implementation. It's intended as a starting point for your own implementation, so you'll need to do some work yourself to use it - including (but not necessarily limited to):

  • Add ember-master-tab to your Ember project, if you're not already using it
    • Switch to the master branch if you're running v1.0.0 or earlier
  • Add the files in this gist (excluding this readme) to the indicated locations in your Ember app
    • Strip out the comment from the top of silent-callback.html - it's just there to tell you where to put the file
    • You may also want to remove the filename comments from the other files, but they're not critical
  • Configure the relevant parameters (oauth endpoint, client ID, requested scope)
    • client ID is currently pulled from Ember environment config (oauthClientID) - either set it there or adjust to suit
  • Send users to the authorize endpoint, with a redirect URI pointing to the callback route, in order to authenticate
  • Implement a login-failed route which displays errors (based on the error query parameter, if desired)
// app/routes/callback.js
import Ember from 'ember';
import OAuth2ImplicitGrantCallbackRouteMixin from 'ember-simple-auth/mixins/oauth2-implicit-grant-callback-route-mixin';
const { Route } = Ember;
export default Route.extend(OAuth2ImplicitGrantCallbackRouteMixin, {
authenticator: 'authenticator:oauth2-implicit-grant',
handleError:'error', function() {
let err = this.get('error');
if (err && err.length) {
// app/authenticators/oauth2-implicit-grant.js
import Ember from 'ember';
import OAuth2ImplicitGrant from 'ember-simple-auth/authenticators/oauth2-implicit-grant';
import OAuth2ImplicitGrantMixin from 'ember-simple-auth/mixins/oauth2-implicit-grant-callback-route-mixin';
import config from '../config/environment';
const { inject: { service }, isEmpty, isPresent } = Ember;
export default OAuth2ImplicitGrant.extend(OAuth2ImplicitGrantMixin, {
masterTab: service(),
session: service(),
routing: service('-routing'),
tokenPropertyName: 'access_token',
tokenExpireName: 'exp',
refreshAccessTokens: true,
refreshLeeway: 240,
refreshFrame: null,
backupTimer: null,
authorizeUrl: 'http://localhost:8080/oauth/authorize',
requestedScope: 'global',
init() {
// Set up callback for hidden iframe refresh flow
window._ESA_Oauth2_Process_Implicit_Refresh = (hash) => {
let frame = this.get('refreshFrame');
let args = this._parseResponse(hash);
let backupTimer = this.get('backupTimer');
let router = this.get('routing');
// Authenticate using provided hash data
this.get('session').authenticate('authenticator:OAuth2ImplicitGrant', args).catch((err) => {
// All the routers!
router.router.router.transitionTo('login-failed', {queryParams: {error: 'session_timeout'}});
// Remove iframe / reset reference
if (!!frame) {
this.set('refreshFrame', null);
// Remove backup schedule
if (!!backupTimer) {;
this.set('backupTimer', null);
authenticate(hash) {
// Use parent authenticator, then set up for token refresh as appropriate
return this._super(...arguments).then((data) => {
return this.initTokenRefresh(data)
restore(data) {
return this.initTokenRefresh(data);
initTokenRefresh(data) {
const dataObject = Ember.Object.create(data);
return new Ember.RSVP.Promise((resolve, reject) => {
const now = this.getCurrentTime();
const token = dataObject.get(this.tokenPropertyName);
let expiresAt = dataObject.get(this.tokenExpireName);
if (isEmpty(token)) {
return reject(new Error('empty token'));
if (isEmpty(expiresAt)) {
// Fetch the expire time from the token data since `expiresAt`
// wasn't included in the data object that was passed in.
const tokenData = this.getTokenData(token);
expiresAt = tokenData[this.tokenExpireName];
if (isEmpty(expiresAt)) {
return resolve(data);
if (expiresAt > now) {
const wait = expiresAt - now - this.refreshLeeway;
if (wait > 0) {
if (this.refreshAccessTokens) {, 'scheduleAccessTokenRefresh', wait);
return resolve(data);
} else if (this.refreshAccessTokens) {
// Token is still valid, but due to be refreshed.
// Try getting a new one - session will be updated if successful., 'refreshAccessToken');
return resolve(data);
} else {
return reject(new Error('unable to refresh token'));
} else if (isPresent(token)) {
// Fetch the expire time from the auth token data.
const tokenData = this.getTokenData(token);
let tokenExpiresAt = tokenData[this.tokenExpireName];
if (isPresent(tokenExpiresAt)) {
if (tokenExpiresAt > now && this.refreshAccessTokens) {
// Token is still valid, but due to be refreshed.
// Try getting a new one - session will be updated if successful., 'refreshAccessToken');
return resolve(data);
} else {
return reject(new Error('Token is expired'));
} else {
return reject(new Error('token is expired'));
Returns the current time as a timestamp in seconds
@method getCurrentTime
@return {Integer} timestamp
getCurrentTime() {
return Math.floor((new Date()).getTime() / 1000);
Returns the decoded token with accessible returned values.
@method getTokenData
@return {object} An object with properties for the session.
getTokenData(token) {
const payload = token.split('.')[1];
const tokenData = decodeURIComponent(window.escape(atob(payload)));
try {
return JSON.parse(tokenData);
} catch (e) {
return tokenData;
* Triggers a token refresh, if this is the master tab.
* Otherwise, triggers a master tab contest in case the master tab has died.
refreshAccessToken() {
const masterTab = this.get('masterTab'); => {
}).else(() => {
Attempts to silently fetch a new access token using prompt=none
@method refreshAccessToken
doRefreshAccessToken() {
let clientId = config.oauthClientID;
let redirectURI = `${window.location.origin}/silent-callback.html`;
let responseType = `token`;// `token id_token`
let scope = this.get('requestedScope');
let authorizeUrl = this.get('authorizeUrl');
let refreshUrl = `${authorizeUrl}?`
+ `client_id=${clientId}`
+ `&redirect_uri=${redirectURI}`
+ `&response_type=${responseType}`
+ `&scope=${scope}&state=foobar123`
+ `&prompt=none`
let frame = this.get('refreshFrame');
if (!!frame) {
// Refresh already in progress?
frame = Ember.$(`<iframe style="display:none" src="${refreshUrl}"></iframe>`);
this.set('refreshFrame', frame);
scheduleAccessTokenRefresh(delay) {
let leeway = this.get('refreshLeeway');
let additionalDelay = Math.floor(0.5 * leeway);
// Schedule main refresh attempt, 'refreshAccessToken', delay * 1000);
// Schedule backup refresh attempt (in case master tab fails / is replaced)
let timer =, 'refreshAccessToken', (delay + additionalDelay) * 1000);
this.set('backupTimer', timer);
<!-- public/silent-callback.html -->
<!doctype html>
<title>Silent callback</title>
<script type="text/javascript">
<h1>Silent callback</h1>

This comment has been minimized.

WillEngler commented May 26, 2017

// All the routers!
router.router.router.transitionTo('login-failed', {queryParams: {error: 'session_timeout'}});

I got a kick out of that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment