Most web applications require some form of user authentication. If you have ever developed a Ruby on Rails (RoR) application, it is likely that you have used an authentication gem such as Devise to handle user authentication. However, with the growing popularity of client-side MVC frameworks such as Ember.js, how do you leverage these gems in your application? This blog post will walk through setting up an ember-rails application using Devise for authentication. You can find the code for this tutorial here.
First, generate a new Rails application:
Next, add Devise to your Gemfile and run ‘bundle install’.
Install Devise by running the following command. Be sure to follow the setup instructions included in the command output. Although, it doesn’t exist yet, set the root route to ‘posts#index’.
Next, generate a user model which will be used by devise:
Next, generate a resource which will require user authentication. Let’s do the classic blog post example:
In the posts controller, require authentication for all actions except for index and show:
Add a ‘Sign Out’ button to application.html.erb.
At this point, we have a normal RoR application in which only an authenticated user can create and edit blog posts. It’s important to note that with the default Devise settings, anyone can sign up and have the ability to create and edit blog posts. However, for the purpose of demonstrating how to setup an ember-rails app with authentication, this will suffice.
Before continuing to the next section, run the app and perform the following:
1. Visit http://localhost:3000.
1. Click ‘New Post’ and notice that you are redirected to the ‘Sign in’ page.
1. Click ‘Sign up’ and create a new user.
1. Try creating a few posts.
1. Make sure the ‘Edit’ action also requires an authenticated user.
Add the following gems to your Gemfile and run ‘bundle install’.
Run the following command to install ember-rails.
We need a new controller to render an empty application layout to load our Ember application. Generate an assets controller with an index action. Delete the contents of the generated view.
Update the root route to point at the newly generated controller.
Add an application template. And the application template displayed on the page.
Add Posts to Ember
First add a Ember model for posts:
Add a few resources to the Ember router:
Add a template for posts:
Add a template for post:
Add a route for posts:
Add a route for post:
Add an application route which will redirect to the posts route.
Active Model Serializers
If we inspect the ‘Network’ tab in Chrome’s Developer Tools, we can see that Ember is successfully getting the posts from the Rails back end, so what’s the problem? The problem is that Ember expects the JSON returned from the server to be formatted in a specific way. Fortunately for us, there is a gem called Active Model Serializers which will allow us to format our JSON in the correct way. Add Active Model Serializers to your Gemfile and run ‘bundle install’.
Add a serializer for posts:
We also need to update the index action of our posts controller to explicitly call ‘render :json’.
We also need to update the create action to render json:
Create and Update Posts
Update the Ember router:
Add templates for new and edit:
Update the posts and post templates with links to the new templates:
Add a PostNewController and a PostEditController which will handle creating and editing posts:
We also need a route for post edit so the model data will show up in the form.
Make sure you are signed out by clicking the sign out button at the top of the page. Try creating and editing some posts. As expected, the Rails back end rejects the requests from Ember and returns a 401 unauthorized status code. Now we need a way to sign in with Ember.
Add User Sign In
Add routes for sign in sign out.
Add a sign in template:
Add a sign in controller:
Add a sign in link to the application template.
At this point if we try signing in, we will get a 406 not acceptable from the Rails back end. This is because Devise doesn’t respond to JSON requests by default. To get this working, we need to override the Devise sessions controller.
We tell Devise to use our custom sessions controller by updating the Rails router.
Cross-Site Request Forgery
Now we can sign in, but if we try to create or edit posts, the Rails back end will return a 422 unprocessabile entity status code. This is because we are not sending the cross-site request forgery token. Add the following function to store.js:
Why refresh the page?
If you look closely at the SignInController, you will see that we are refreshing the page on sign in using ‘location.reload()’. We are doing this because when the user signs in, the session is reset and the cross-site request forgery token is regenerated. Refreshing the page allows us to get the new token. Although this is not ideal, it’s the easiest way of solving this problem short of disabling forgery protection. If you find a better way of doing this, please let me know in the comments.
Add User Sign Out
At this point, we can sign in, create posts, and edit posts. The only thing that is missing is the ability to sign out. First, we need to create an application controller with a property called signedIn.
Because we are refreshing the page on sign in, we cannot toggle this property on sign in. We need to include the logic in the application route before the application template is rendered. By sending an empty post request to /users/sign_in we can tell if we are signed in based on the response from the Rails back end and set the property appropriately.
Now we can use this new property in our application template to display the ‘Sign In’ link if the user is not signed in or the ‘Sign Out’ link if the user is signed in.
Next we need to add the functionality that will actually sign out the user. This will destroy the user session on the Rails back end and refresh the page so we can get the new cross-site request forgery token.
Now that sign out is working, we can remove the ‘Sign Out’ button from the Rails application layout.
This blog post has demonstrated how to add authentication to an Ember-Rails application with Devise. The next step would be clean up our controllers so they don’t render unnecessary HTML. Another improvement would be the ability to sign in without refreshing the page.