ESA: How to redirect to corporate login page

I’m using ESA implicit grant authentication. If a user is not logged in I display just an empty application template with a header containing “Login” button. When clicking on Login button, I redirect the user to the corporate login page:

# routes/application.js

actions: {
    login: function() {
      let oauthUrl = config.oauthUrl;
      let clientId = config.clientID;
      let redirectURI = `${window.location.origin}/callback`;
      let responseType = `token`;
      let scope = `profile%20openid`;
      window.location.replace(oauthUrl
                            + `?client_id=${clientId}`
                            + `&redirect_uri=${redirectURI}`
                            + `&response_type=${responseType}`
                            + `&scope=${scope}`
      );
    },
    logout() {
      this.get('session').invalidate();
    }
  },

  beforeModel() {
    return this._loadCurrentUser();
  },

  sessionAuthenticated() {
    this._super(...arguments);
    this._loadCurrentUser();
  },

  _loadCurrentUser() {
    return this.get('currentUser').loadCurrentUser().catch(() => this.get('session').invalidate());
  }

I also defined current-user.js service as follows:

# services/current-user.js

import RSVP from 'rsvp';
import Service, { inject as service } from '@ember/service';

export default Service.extend({
  session: service('session'),
  store: service(),
  flashMessages: service('flash-messages'),
  i18n: service('i18n'),
  currentShop: service('current-shop'),

  loadCurrentUser() {
    this.get('flashMessages').clearMessages();
    if (this.get('session.isAuthenticated')) {
      return this.get('store').queryRecord('user', { me: true }).then((user) => {
        this.get('flashMessages').success(this.get('i18n').t('flash.signed_in'));
        this.get('currentShop').setShop(user.get('shop'));
        this.set('user', user);
      });
    } else {
      this.get('flashMessages').info(this.get('i18n').t('flash.signed_off'));
      return RSVP.resolve();
    }
  }
});

How is it possible to redirect a user to the corporate login page without clicking the Login button ? Thank you.

Setup the login action as a method on your route, in the beforeModel() hook check if the session is invalid, and if it is, run the login method to take the user away to the corporate login screen.

But as you can see, I already have login action in my application route. So instead of calling and returning return this._loadCurrentUser();, I’ll have just to:

  • check
if (!this.get('session.isAuthenticated')) {
  login()
}

Right ?

Actions are things that are going to be used in a template, so the same way you have _loadCurrentUser defined you’d want to have login move out of the actions object. (I’d also rename it to gotoLogin)

You can then do

if (!this.get('session.isAuthenticated')) {
  this.gotoLogin()
}

in your beforeModel() hook.

So here is the modified version of application route:

export default Route.extend(ApplicationRouteMixin, {
  currentUser: service('current-user'),

  actions: {
    logout() {
      this.get('session').invalidate();
    }
  },

  beforeModel() {
    if (!this.get('session.isAuthenticated')) {
      this._goToLogin();
    } else {
      return this._loadCurrentUser();
    }
  },

  sessionAuthenticated() {
    this._super(...arguments);
    this._loadCurrentUser();
  },
  
  _goToLogin() {
    let oauthUrl = config.oauthUrl;
    let clientId = config.clientID;
    let redirectURI = `${window.location.origin}/callback`;
    let responseType = `token`;
    let scope = `profile%20openid`;
    window.location.replace(oauthUrl
                          + `?client_id=${clientId}`
                          + `&redirect_uri=${redirectURI}`
                          + `&response_type=${responseType}`
                          + `&scope=${scope}`
    );
  },

  _loadCurrentUser() {
    return this.get('currentUser').loadCurrentUser().catch(() => this.get('session').invalidate());
  }

I also forgot to tell that I 'd defined routeAfterAuthentication hook in environment.js:

ENV['ember-simple-auth'] = {
    routeAfterAuthentication: 'dashboard'
  };

And in my index.js route there is also beforeModel hook:

# routes/index.js

export default Route.extend({
  session: service('session'),

  beforeModel: function() {
    if (this.get('session.isAuthenticated')) {
      this.transitionTo('dashboard');
    }
  }
});

With these modifications there 2 issues:

  • First time I am successfully redirected to the corporate login page. But after being authenticated and redirected back to dashboard page, its content is empty and I had to explicitly click on dashboard link in the side menu to display it;
  • Logout action does not work any more: pages is reloading several times and I’m again logged in.

Are there any errors in your console when it redirects to dashboard? Is the url updated to match the route? is the sessionAuthenticated method definitely being ran when your login page redirects you?

The logout action should be in your application controller rather than the route. I’m not actually sure how it would have worked before being defined in the route.

@evoactivity Thank you for your time :). As for the url, - no, it didn’t change after I applied the above modifications and was:

http://localhost:4200/callback#access_token=XXXXXX&token_type=Bearer&expires_in=7199 

instead of

localhost:4200/dashboard

When I click on Logout button, after several refreshing, I’m still logged in and staying on the same page (empty) and with the same URL:

http://localhost:4200/callback#access_token=XXXXXX&token_type=Bearer&expires_in=7199

and there is no errors in the browser console, nothing at all :frowning:
What is weird, Firefox displays it nevertheless after clicking on Logout button. But in Chrome and Safari, clicking on logout results in endless refreshing.

Do you have a route setup to handle callback ? I’ve not use implicit grant before so not sure on the specifics, does ESA handle callback for you?

Yes, ESA does it, I just copy-pasted the code they use in their dummy app:

# routes/callback.js

import Route from '@ember/routing/route';
import OAuth2ImplicitGrantCallbackRouteMixin from 'ember-simple-auth/mixins/oauth2-implicit-grant-callback-route-mixin';

export default Route.extend(OAuth2ImplicitGrantCallbackRouteMixin, {
  authenticator: 'authenticator:oauth2-implicit-grant'
});

Callback is defined with other routes:

# router.js

Router.map(function() {
  this.route('auth-error');
  this.route('callback');
  this.route('dashboard');
  this.route('information');
  this.route('address');
  this.route('phone-fax');
  this.route('social');
  this.route('links');
  this.route('description');
  this.route('shop-languages');
  this.route('working-hours');
  this.route('holiday-hours');
});

Ok, I’ve cloned the ESA repo and had a play with the implict grant dummy app. You can reset everything back to how you had it.

Move the login action to the index route so it looks like this


import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import config from '../config/environment';
import isFastBoot from 'ember-simple-auth/utils/is-fastboot';

export default Route.extend({
  session: service('session'),
  _isFastBoot: isFastBoot(),

  beforeModel() {
    if (this.get('session.isAuthenticated')) {
      this.transitionTo('dashboard');
    } else {
      if (!this.get('_isFastBoot')) {
        let oauthUrl = config.oauthUrl;
        let clientId = config.googleClientID;
        let redirectURI = `${window.location.origin}/callback`;
        let responseType = `token`;
        let scope = `email`;
        window.location.replace(oauthUrl
                              + `client_id=${clientId}`
                              + `&redirect_uri=${redirectURI}`
                              + `&response_type=${responseType}`
                              + `&scope=${scope}`
        );
      }
    }
  }
});

You might not need the fastboot check.

When the application loads the index route it will either try to redirect to the dashboard or load the login page/log them in.

Hi ! Thank you for your reply. I think I’ll have to remove fastBoot as I have the error:

Error: Could not find module `ember-simple-auth/utils/is-fastboot` imported from `decastore-front/routes/index`

May be it is because of the version 1.4.2of ESA that I’m using.

After removing it, the same problems:

  • redirect and login work
  • logout - FAILS → refresh → I’m logged in again.

Did you move the logout action into the controller/component that uses the action or is it still in the route?

Logout action is still in application route. Should I move it to application controller ?

Updated ESA to 1.6.0, rollbacked to use fastBoot and moved logout to application controller, - still the same behaviour :frowning:

What template is your logout button in? Is it in a component or directly in the application template? You want the logout action in the controller or component that powers the template the action is used in.

I have the logout button in application template:

<ul class="navbar-nav justify-content-end">
      {{#if session.isAuthenticated}}
        {{#if currentUser.user}}

          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
              aria-haspopup="true" aria-expanded="false">
              <span class="oi oi-person" title="user" aria-hidden="true"></span>
              {{currentUser.user.username}}
            </a>
            <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
              <a {{action 'logout'}} class="dropdown-item">{{t 'navbar.logout'}}</a>
            </div>
          </li>
        {{/if}}
      {{/if}}
    </ul>

As I don’t have a component defined for that, I think application controller would be good to put it.

After clicking on logout button, URL keeps being as http://localhost:4200/callback#access_token=XXXXXX&token_type=Bearer&expires_in=7199 for 1-2 seconds and then it changes again for localhost:4200/dashboard and I can see that I’m logged in again.

That would be expected, the logout only invalidates the client side session, if your session with the oauth provider is still valid the application will forward to the oauth login url and the provider will instantly post back to your callback to log you in, you may need to implement some other logic to invalidate the session with your oauth provider if you want to be taken back to a login page.

Weird… Why this worked as needed with Login button ? (I mean that I could log out) Do you mean that I have to implement some solution to clear the session on the backend side ?

I pushed it to GitHub repo, if someone could take a look, it would fine. Thank you.

I tried to override invalidate method in the authenticator as follows:

# authenticators/oauth2-implicit-grant.js

import OAuth2ImplicitGrant from 'ember-simple-auth/authenticators/oauth2-implicit-grant';
import { inject as service } from '@ember/service';

export default OAuth2ImplicitGrant.extend({
  currentUser: service(),

  invalidate(data, args) {
    this._super(...arguments);
    let currentUser = args.get('user');
    return this.get('currentUser').logoutUser(currentUser);
  }
});

I also removed completely index rote and moved the redirection to appplication route where I also overridden invalidationSucceeded method:

import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
import config from '../config/environment';

export default Route.extend(ApplicationRouteMixin, {
  currentUser: service('current-user'),

  actions: {
    logout() {
      this.get('session').invalidate(this.get('currentUser'));
    }
  },

  beforeModel() {
    if (!this.get('session.isAuthenticated')) {
      let oauthUrl = config.oauthUrl;
      let clientId = config.clientID;
      let redirectURI = `${window.location.origin}/callback`;
      let responseType = `token`;
      let scope = `profile%20openid`;
      window.location.replace(oauthUrl
        + `?client_id=${clientId}`
        + `&redirect_uri=${redirectURI}`
        + `&response_type=${responseType}`
        + `&scope=${scope}`
      );
    } else {
      return this._loadCurrentUser();
    }
  },

  invalidationSucceeded() {
    this._super(...arguments);
    let oauthUrl = config.oauthUrl;
    let clientId = config.clientID;
    let redirectURI = `${window.location.origin}/callback`;
    let responseType = `token`;
    let scope = `profile%20openid`;
    window.location.replace(oauthUrl
      + `?client_id=${clientId}`
      + `&redirect_uri=${redirectURI}`
      + `&response_type=${responseType}`
      + `&scope=${scope}`
    );
  },

  _loadCurrentUser() {
    return this.get('currentUser').loadCurrentUser().catch(() => this.get('session').invalidate(this.get('currentUser')));
  }
});

and still noting changed:

  • my logout action is fired correctly in the back-end
  • but I’m re-logged in again automatically
  • there is no redirection to dashboard route any more, it seems like the settings in environmenthas no effect:
#environment.js
ENV['ember-simple-auth'] = {
    routeAfterAuthentication: 'dashboard'
  };

The lates modifications are on a separate branch. Does anybody has an idea ? Thank you.