I was asked to implement a Shibboleth authentication (EduID) in our EmberJS application at the Databases Education Laboratory, Budapest University of Technology and Economics. As the backend was already done my task contained the following subtasks:

  1. put a button on the login page
  2. on pressing the button redirect to the Shibboleth page, that is provided by the backend
  3. have a separate route where the Shibboleth page can redirect to after a successful login
  4. get a token from the backend for a new session
  5. handle errors

The list of subtasks and the solution was created together with József Marton.

Button on the login page

A simple HTML snippet had to be included with some Handlebars actions.

1
2
3
  <button type="button" name="button" {{action 'loginBMERedirect'}}>
    Login using my Edu ID
  </button>

Redirecting

Now that we have the button I just included the implementation of the action loginBMERedirect. As we both have a test and a production server I had to make sure not to hardcode the base url. Therefore I used window.location.origin to fetch that information. Then I have a variable for the endpoint of the Shibboleth login, one for the target (which is in our case the base url and a specific route). The url is based on base url + Shibboleth login endpoint + target as query parameter. Because the target has a # in the url I had to encode it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  import Ember from 'ember';
  import config from '../config/environment';

  export default Ember.Component.extend({
      loginBMERedirect() {
        var currentWebsite = window.location.origin;
        var shibboleth = '/Shibboleth.sso/Login';
        var target = currentWebsite + '/#/login-shibboleth';
        var shibbolethUrl = currentWebsite + shibboleth
          + "?target=" + encodeURIComponent(target);

        window.location.replace(shibbolethUrl);
        return false;
      }
    }
  });

Shibboleth login route

The target of the Shibboleth login page is a specific route where, before the model is loaded, I have to fetch a token for a new session. To create such a route I used Ember CLI.

1
  ember generate route login-shibboleth

Token

Okay, we have a new route. Now we have to get a token and redirect to a specific page. In our application we use ember-simple-auth for authentication. I created a new authenticator called shibboleth because I needed to do some specific Ajax call. In this authenticator I overrode the base authenticate function. The token endpoint is stored in a configuration file and the authenticate function returns a promise.

1
2
3
4
5
6
7
8
9
10
11
12
13
  import Ember from 'ember';
  import Base from 'ember-simple-auth/authenticators/base';
  import config from '../config/environment';

  export default Base.extend({
    ajax: Ember.inject.service(),

    authenticate() {
      return this.get('ajax').request(
        config['ember-simple-auth-token'].serverTokenShibbolethEndpoint
        );
    }
  });

And then I called this authenticator before we load the model. I used this function to pause the transition while I get the token. And then to redirect to the next page depending on a successful or an unsuccessful session authentication.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import Ember from 'ember';
import UnauthenticatedRouteMixin
  from 'ember-simple-auth/mixins/unauthenticated-route-mixin';
import ErrorRouteMixin from '../mixins/error-route';

export default Ember.Route.extend(ErrorRouteMixin,  {
  session: Ember.inject.service('session'),
  beforeModel() {
    this._super(...arguments);
    var session = this.get('session')
      .authenticate('authenticator:shibboleth')
      .then(()=>{
        this.transitionTo('settings');
      }).catch((e) => {
        if ((e['errors'] !== undefined) &&
            (e['errors'].length > 0) &&
            (e['errors'][0]['title'] !== undefined)) {
          this.transitionTo('login', {
            queryParams: {
              errorMessage: "Shibboleth login error: " + e['errors'][0]['title']
              }
            });
        }
        else {
          this.transitionTo('login', {
            queryParams: {
              errorMessage: "Shibboleth login error"
            }
          });
        }
    });
  }
});

If the authentication is successful I redirect to the settings page. If it’s unsuccessful, I redirect to the default login page with an error message. I make sure that if there is an error message from the backend I append it to the default error message.

Token format

1
2
3
  {
    "token": "your_new_token"
  }

Handle errors

We are almost done, but I wanted to make sure that our error message shows up on the screen. To do so I added a query parameter to the login page’s route to handle the message I sent during transition. I also make sure that it has a default value.

1
2
3
4
5
6
  import Ember from 'ember';

  export default Ember.Controller.extend({
    queryParams: ['errorMessage'],
    errorMessage: ""
  });

I have to pass down this error message to the component that contains the different ways to login.

1
2
3
4
5
6
7
  {{#if errorMessage}}
    {{login-box goToSettings=(action 'goToSettings')
      redirect=redirect errorShibboleth=errorMessage}}
  {{else}}
    {{login-box goToSettings=(action 'goToSettings')
      redirect=redirect }}
  {{/if}}

Then I make sure to show it in the login-box component’s handlebars file.

1
2
3
4
5
6
7
8
  <button type="button" name="button" {{action 'loginBMERedirect'}}>
    Login using my Edu ID
  </button>
  {{#if errorShibboleth}}
    <div class="error-box">
      {{errorShibboleth}}
    </div>
  {{/if}}

The codebase can be found on GitHub.