Skip to content

Instantly share code, notes, and snippets.

@bummzack
Last active November 20, 2016 19:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bummzack/681925d94f61429ca659 to your computer and use it in GitHub Desktop.
Save bummzack/681925d94f61429ca659 to your computer and use it in GitHub Desktop.
Authentication using Ember simple auth and SilverStripe RESTfulAPI

Authentication using Ember simple auth and SilverStripe RESTful API

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

Prerequisites

Here's a list of the software that was used:

Frontend

  • 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

Backend (SilverStripe)

Setting up SilverStripe

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

Setting up an ember application to load data via RESTful API

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'
});

Setting up client-side auth with ember simple auth

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';
}

The custom authenticator

// 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);
                });
            });
        });
    }
});

The custom authorizer

// 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);
        }
    }
});

Updating your config file

Add the following to config/environment.js

ENV['simple-auth'] = {
    authorizer: 'authorizer:ss-rest-authorizer'
};

Updating your routes

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:

Create the login controller and template

// 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}}

Updating application.hbs

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}}
@Timer91
Copy link

Timer91 commented Jun 19, 2016

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.
screen shot 2016-06-19 at 22 51 09
At line 8, I have tried many things like this.get( "session.secure.access_token" ) or this.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

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