Skip to content

Instantly share code, notes, and snippets.

@colymba
Forked from bummzack/restful_simpleauth.md
Last active August 29, 2015 14:20
Show Gist options
  • Save colymba/bfafae0c4162b05d87a2 to your computer and use it in GitHub Desktop.
Save colymba/bfafae0c4162b05d87a2 to your computer and use it in GitHub Desktop.

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

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}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment