Edward
Edward

Reputation: 141

EmberJS Rails API security

Setup is an Ember frontend with a rails backend using JSON api.

Everything is going fine but some questions do come up:

How do I ensure only the emberjs application consumes the api? I wouldn't want a scripter to write an application to consume the backend api.

It all seems pretty insecure because the EmberJS application would come in a .js file to the client.

How would I ensure a user is really that user if everyone has access to a JS console?

Upvotes: 1

Views: 546

Answers (2)

Andrew Hacking
Andrew Hacking

Reputation: 6366

I am using Ember Simple Auth to great effect for user authentication and API authorisation.

I use the Oauth 2 user password grant type for authentication of the user and authorising the application by way of a bearer token which must be sent on all future API requests. This means the user enters their username/email and password into the client app which then sends to the server via HTTPS to get an authorisation token and possibly a refresh token. All requests must be over HTTPS to protect disclosure of the bearer token.

I have this in app/initializers/auth:

Em.Application.initializer
  name: 'authentication'
  initialize: (container, application) ->
    Em.SimpleAuth.Authenticators.OAuth2.reopen
      serverTokenEndpoint: 'yourserver.com/api/tokens'
    Em.SimpleAuth.setup container, application,
      authorizerFactory: 'authorizer:oauth2-bearer'
      crossOriginWhitelist: ['yourserver.com']

In app/controllers/login.coffee:

App.LoginController = Em.Controller.extend Em.SimpleAuth.LoginControllerMixin,
  authenticatorFactory: 'ember-simple-auth-authenticator:oauth2-password-grant'

In app/routes/router.coffee:

App.Router.map ->
  @route 'login'
  # other routes as required...

In app/routes/application.coffee:

App.ApplicationRoute = App.Route.extend Em.SimpleAuth.ApplicationRouteMixin

In app/routes/protected.coffee:

App.ProtectedRoute = Ember.Route.extend Em.SimpleAuth.AuthenticatedRouteMixin

In templates/login.hbs (I am using Ember EasyForm):

{{#form-for controller}}
  {{input identification
          label="User"
          placeholder="[email protected]"
          hint='Enter your email address.'}}
  {{input password
          as="password"
          hint="Enter your password."
          value=password}}
  <button type="submit" {{action 'authenticate' target=controller}}>Login</button>
{{/form-for}}

To protect a route I just extend from App.ProtectedRoute or use the protected route mixin.

Your server will need to handle the Oauth 2 request and response at the configured server token endpoint above. This is very easy to do, Section 4.3 of RFC 6749 describes the request and response if your server side framework doesn't have built-in support for Oauth2. You will need to store, track and expire these tokens on your server however. There are approaches to avoiding storage of tokens but that's beyond the scope of the question :)

I have answered the backend question and provided example rails example code for user authentication, API authorisation and token authentication here

Upvotes: 0

MilkyWayJoe
MilkyWayJoe

Reputation: 9092

You can extend the RESTAdapter and override the ajax method to include your authentication token in the hash, and you need make sure your controllers validate that token.

In my environment (.NET), I have the authentication token in a hidden field of the document which my app renders, so my ajax override looks like this:

App.Adapter = DS.RESTAdapter.extend({
  ajax: function(url, type, hash, dataType) {
      hash.url = url;
      hash.type = type;
      hash.dataType = dataType || 'json';
      hash.contentType = 'application/json; charset=utf-8';
      hash.context = this;
      if (hash.data && type !== 'GET') {
        hash.data = JSON.stringify(hash.data);
      }
      var antiForgeryToken = $('#antiForgeryTokenHidden').val();
      if (antiForgeryToken) {
          hash = {
            'RequestVerificationToken': antiForgeryToken
          };
      }
      jQuery.ajax(hash);
    }
});

The token can come from a cookie or whatever you define, as long as you're able to include it in the request header and have your controllers validate it (possibly in before_filter), it should enough.

Then in the Store, pass the new adapter instead of the default (which is RESTAdapter)

App.Store = DS.Store.extend({
    revision: 12,
    adapter: App.Adapter.create()
})

Note: RESTAdapter#ajax will be changed in favor or Ember.RSVP, making this override deprecated. It must be updated after the next release, but should be ok for revision 12.

Upvotes: 1

Related Questions