Shibboleth authentication in EmberJS
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:
- put a button on the login page
- on pressing the button redirect to the Shibboleth page, that is provided by the backend
- have a separate route where the Shibboleth page can redirect to after a successful login
- get a token from the backend for a new session
- 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.