1 year ago
Update:I released an Ember.js plugin that makes it very easy to implement an authentication system as described in this post: Ember.SimpleAuth.
When we started our first ember.js project in June 2013, one of the first things we implemented was authentication. Now, almost 2 months later, it has become clear that our initial approach was not really the best and had some shortcomings. So I implemented a better authentication (mostly based on the embercasts on authentication).
The basic approach is still the same as in our initial implementation - we have a
/session route in our Rails app that the client
POSTs its credentials to and if those are valid gets back an authentication token together with an id that identifies the user’s account on the server side.
This data is stored in a "session" object on the client side (while technically there is no session in this stateless authentication mechanism, I still call it session in absence of an idea for a better name). The authentication token is then sent in a header with every request the client makes.
The client "session"
The "session" object on the client side is a plain
Ember.Object that simply keeps the data that is received from the server on session creation. It also stores the authentication token and the user’s account ID in cookies so the user doesn’t have to login again after a page reload (As Ed points out in a comment on the old post it’s a security issue to store the authentication token in a cookie without the user’s permission - I think using a session cookie like I do should be ok as it’s deleted when the browser window is closed. Of course you could also use sth. like localStorage like Marc points out below). I’m creating this object in an initializer so I can be sure it exists (of course it might be empty) when the application starts.
When this has run I can always access the current "session" information as
App.Session. Notice the
.create() at the end of the initializer that creates an instance of the
Ember.Object right away. When we need to check whether a user is authenticated we can simply check for presence of the
authToken property. Of course we could add a
isAuthenticated() method that could perform additional checks but we didn’t have the need for that yet.
This "session" object will also load the actual account record from the server if the
authAccountId is set (
this.set('authAccount', App.Account.find(authAccountId));. This allows us to e.g. use
App.Session.authAccount.fullName in our templates to display the user’s name or similar data.)
To actually use the
authToken when making server requests, we register an AJAX prefilter that adds the authentication token in a header as long as the request is sent to our domain:
The Rails server can then find the authenticated user by the token in the header:
As described above, the login API is a simple
/session route on the server side that accepts the user’s login and password and responds with either HTTP status 401 when the credentials are invalid or a session JSON when the authentication was successful. On the client side we have routes for creating and destroying the session:
SessionNewController only needs one action
login that sends the entered credentials and acts according to the server’s response - if the server responds successfully it reads the session data from the response and updates the
App.Session object accordingly. It also checks whether there is an attempted transition that was intercepted due to missing authentication and retries that if it exists (This is the case where the user tries to access a certain page without having authenticated, is redirected to the login form, logs in and is redirected again to the initially requested page).
.failhandler as well that display an error message.
The template is just a simple form (actual elements, classes etc. of course depend on your specific application:)
Logging out is actually pretty simple as well. The client just sends a
DELETE to the same
/session route that makes the server reset the authentication token in the database so that the token on the client side is invalidated. The client also deletes the saved session information in
App.Session so there’s no stale data.
As this action should be triggered as soon as the user enters the
/#/session/destroy route, we have a simple route implementation that triggers the action upon route activation:
To easily enable authentication for any route in the application, I created an
App.AuthenticatedRoute that extends
Ember.Route and that all routes that need to enforce user authentication can extend again:
redirectToLogin sets the
App.Session so that the user will be redirected to the initially requested page after successfulyl logging in.
This is better authentication with ember.js - enjoy!