The goal of this GIST is to give a short summary of what is needed to perform authentication from an ember.js application using simple-auth and the SilverStripe RESTful API module
Here's a list of the software that was used:
- Ember.js (v 1.11.1), using Ember CLI (v 0.2.3)
- Ember simple auth (v 0.8.0-beta.2), installed via ember-cli-simple-auth
- SilverStripe 3.1
- RESTful API Module, installed via composer
Just installing the RESTful API Module is enough. I added the following config into a file named mysite/_config/restconfig.yml
:
---
Name: mysite
After: 'framework/*','cms/*'
---
# MyDataObject is an example to set API access for a DataObject
MyDataObject:
api_access: true
Member:
extensions:
- RESTfulAPI_TokenAuthExtension
# RestfulAPI config
RESTfulAPI:
authentication_policy: true
access_control_policy: 'ACL_CHECK_CONFIG_AND_MODEL'
dependencies:
authenticator: '%$RESTfulAPI_TokenAuthenticator'
authority: '%$RESTfulAPI_DefaultPermissionManager'
queryHandler: '%$RESTfulAPI_DefaultQueryHandler'
serializer: '%$RESTfulAPI_EmberDataSerializer'
cors:
Enabled: true
Max-Age: 86400
# Components config
RESTfulAPI_DefaultQueryHandler:
dependencies:
deSerializer: '%$RESTfulAPI_EmberDataDeSerializer'
If you grant API access to a DataObject, make sure to implement proper permission checks. You can check for the RESTful API permissions, similar to this:
public function canEdit($member){
return Permission::check('RESTfulAPI_EDIT', 'any', $member);
}
The permission codes are:
- RESTfulAPI_VIEW
- RESTfulAPI_EDIT
- RESTfulAPI_CREATE
- RESTfulAPI_DELETE
This almost works out of the box. I added an application adapter to point to the correct api endpoint, like so:
// FILE: app/adapters/application.js
import DS from 'ember-data';
import ENV from '../config/environment';
export default DS.RESTAdapter.extend({
// the host is being pulled from the config file
host: ENV.backend,
namespace: 'api'
});
This is slightly more complex. You need to implement a custom Authenticator and a custom Authorizer.
This example is very basic.. what you need is the custom authenticator and authorizer, what you do with your routes etc. is entirely up to you. You just need to perform the login at some point.
Note: This implementation uses the ENV configuration to set the backend host, so don't forget to add backend
to your config/environment.js
. Example:
if (environment === 'development') {
// point to the local silverstripe installation
ENV.backend = 'http://localhost/localsilverstripe';
}
if (environment === 'production') {
ENV.backend = 'http://yourdomain.com';
}
// FILE: app/authenticators/ss-rest-authenticator.js
import Base from 'simple-auth/authenticators/base';
import ENV from '../config/environment';
import Ember from 'ember';
export default Base.extend({
restore: function(data) {
return new Ember.RSVP.Promise(function (resolve, reject) {
if (!Ember.isEmpty(data.token)) {
resolve(data);
}
else {
reject();
}
});
},
authenticate: function(options) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "POST",
url: ENV.backend + '/api/auth/login',
data: JSON.stringify({
email: options.identification,
pwd: options.password
})
}).then(function(response) {
if(!response.result){
Ember.run(function(){
reject(response.message);
});
} else {
Ember.run(function() {
resolve(response);
});
}
}, function(xhr, status, error) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
},
invalidate: function(data) {
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
type: "POST",
url: ENV.backend + '/api/auth/logout'
}).then(function(response) {
Ember.run(function() {
resolve(response);
});
}, function(xhr, status, error) {
Ember.run(function() {
reject(xhr.responseJSON || xhr.responseText);
});
});
});
}
});
// FILE: app/authorizers/ss-rest-authorizer.js
import Base from 'simple-auth/authorizers/base';
import Ember from 'ember';
export default Base.extend({
authorize: function(jqXHR, requestOptions) {
requestOptions.contentType = 'application/json;charset=utf-8';
var token = this.get('session.secure.token');
if (this.get('session.isAuthenticated') && !Ember.isEmpty(token)) {
jqXHR.setRequestHeader('X-Silverstripe-Apitoken', token);
}
}
});
Add the following to config/environment.js
ENV['simple-auth'] = {
authorizer: 'authorizer:ss-rest-authorizer'
};
The application route should extend the simple-auth ApplicationRouteMixin
.
Example:
// FILE: app/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
actions: {
invalidateSession: function() {
this.get('session').invalidate();
}
}
});
Routes that should be protected, should extend AuthenticatedRouteMixin
, example:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(){
// return protected model here
}
});
In your app/router.js
you might want to add a login route:
export default Router.map(function () {
// your other routes here...
this.route('login');
});
After that:
// FILE: app/controllers/login.js
import Ember from 'ember';
export default Ember.Controller.extend({
actions: {
authenticate: function() {
var _this = this;
var credentials = this.getProperties('identification', 'password');
this.get('session').authenticate('authenticator:ss-rest-authenticator', credentials).then(null, function(message) {
_this.set('errorMessage', message);
});
}
}
});
Template:
<h1>Login</h1>
<form {{action 'authenticate' on='submit'}}>
<div class="form-group">
<label for="identification">Login</label>
{{input value=identification placeholder='Enter Login' class='form-control'}}
</div>
<div class="form-group">
<label for="password">Password</label>
{{input value=password placeholder='Enter Password' class='form-control' type='password'}}
</div>
<button type="submit" class="btn btn-default">Login</button>
</form>
{{#if errorMessage}}
<div class="alert alert-danger">
<strong>Login failed:</strong> {{errorMessage}}
</div>
{{/if}}
In your application template you can toggle content, depending on login status, a simple example:
{{#if session.isAuthenticated}}
<a {{ action 'invalidateSession' }}>Logout</a>
{{else}}
{{#link-to 'login'}}Login{{/link-to}}
{{/if}}
{{outlet}}
Hello
I use EmberJS since a few days, so I'm not that qualified with this language.
I have read your tutorial to get some help with Authentication but I have one problem in the Authorizer.
Actually, if I have understood at this line that you can get your token through the session object.
var token = this.get('session.secure.token');
I have tried to do the same in my authorizer.

At line 8, I have tried many things like
this.get( "session.secure.access_token" )
orthis.get( "session.access_token" )
, ... But it is always undefined.However, the jqXHR object contains all my data that I have previously got in my authenticator.
Maybe I misunderstood something, or maybe you stored your data, but it is not in the example.
That is why I am asking you for some help, if you can give me more informations about the session object and how do you get your token through this one.
Thank you