Ruby on Rails and Android Authentication Part One
The Rails Web Application
Intro
In this two-three-part tutorial you’ll learn first how to build an authentication API that can allow external users to register, login and logout through JSON requests. After having successfully logged in, a user will receive an authentication token that could be used in following API requests to authorize the user, securing the access to your application’s resources.
In the second part, you will build an Android application that will be able to consume this API, allowing the user to register and login directly from the app.
With these tutorials I want to explain, with a step-by-step approach, how to build a complete solution that can be used as a base for a more complex scenario.
Supplemental bonuses will be given to enhance both the backend and the Android app with additional nice-to-have features.
Let’s start!
Ingredients
Here’s the list of what we are going to use in this tutorial:
- Rails 3.2 - rubyonrails.org
- Devise - github.com/plataformatec/devise
- ActiveAdmin - activeadmin.info
- Heroku - heroku.com
- Android 4.1 (API 16) - developer.android.com
- UrlJsonAsyncTask - github.com/tonylukasavage/com.savagelook.android
- ActionBarSherlock - actionbarsherlock.com
The Rails Backend
Start by creating a new Rails app. At the moment I’m writing the latest version of rails is 3.2.8, check yours by using rails -v
in the command line.
rails new authexample_webapp
cd authexample_webapp
Devise Setup
Add the Devise gem to your application:
# file: Gemfile
gem 'devise'
Install the gems and create the default user model with the Devise generators.
bundle install
rails generate devise:install
rails generate devise user
Uncomment the following lines in the migration that are relative to the token_authenticatable
module:
# file: db/migrate/<timestamp>_devise_create_users.rb
## Token authenticatable
t.string :authentication_token
add_index :users, :authentication_token, :unique => true
Add the :token_authenticatable
to the devise modules in the user model:
# file: app/models/user.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:token_authenticatable
Add the before filter :ensure_authentication_token
to the user model:
# file: app/models/user.rb
before_save :ensure_authentication_token
And finally uncomment the following line in the Devise initializer to enable the auth_token:
# file: config/initializers/devise.rb
# ==> Configuration for :token_authenticatable
# Defines name of the authentication token params key
config.token_authentication_key = :auth_token
Bonus: add username to the user model
In our Android app we want to give possibility to the user to specify a username in the registration form. Let’s add this column to the table ‘users’ and the attribute to the attr_accesible
list in the user model.
Add the following line to the change
method in the migration.
# file: db/migrate/<timestamp>_devise_create_users.rb
t.string :name, :null => false, :default => ""
# file: app/models/user.rb
attr_accessible :name, :email, :password, :password_confirmation, :remember_me
Bonus: email confirmation from web, skip it from the Android app
The users of our Android app don’t want to wait to use it, so we skip the confirmation email check provided by Devise with the confirmable
module.
If you still want to use the module for the users that register from the webapp, just add this lines to the code.
Uncomment the following lines in the migration that are relative to the confirmable
module:
# file: db/migrate/<timestamp>_devise_create_users.rb
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
add_index :users, :confirmation_token, :unique => true
Add the :confirmable
to the devise available modules in the user model:
# file: app/models/user.rb
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :token_authenticatable
We need a way to bypass the confirmation step after the creation of a new user: setting a date to the confirmed_at
will do this. We add a new method to the user model that will be used in our Api controller.
# file: app/models/user.rb
def skip_confirmation!
self.confirmed_at = Time.now
end
We are done with the setup of our user model, we can now launch the rake tasks to create and migrate the database:
rake db:create db:migrate
API Sessions Controller (login and logout)
Let’s start coding the sessions controller that will be used by our Android app to authenticate the users.
I want to use a namespace and a version for our API, so its controllers will be under the app/controller/api/v1/
folder.
The sessions controller has two actions: create for login and destroy for logout. The first accepts a user
JSON object as POST data with an email
and a password
parameters and returns an auth_token
if the user exists in the database and the password is correct. The logout action expects an auth_token
parameter in the url.
# file: app/controller/api/v1/sessions_controller.rb
class Api::V1::SessionsController < Devise::SessionsController
skip_before_filter :verify_authenticity_token,
:if => Proc.new { |c| c.request.format == 'application/json' }
respond_to :json
def create
warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
render :status => 200,
:json => { :success => true,
:info => "Logged in",
:data => { :auth_token => current_user.authentication_token } }
end
def destroy
warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#failure")
current_user.update_column(:authentication_token, nil)
render :status => 200,
:json => { :success => true,
:info => "Logged out",
:data => {} }
end
def failure
render :status => 401,
:json => { :success => false,
:info => "Login Failed",
:data => {} }
end
end
In the routes definition we add our namespace and the two login and logout routes.
# file: config/routes.rb
namespace :api do
namespace :v1 do
devise_scope :user do
post 'sessions' => 'sessions#create', :as => 'login'
delete 'sessions' => 'sessions#destroy', :as => 'logout'
end
end
end
Test the login
Let’s create a user to test the login with, open the rails console with rails console
in the command line and write:
user = User.new(:name => 'testuser', :email => 'user@example.com', :password => 'secret', :password_confirmation => 'secret')
user.skip_confirmation!
user.save
Close the console and fire up the rails server
and in another command line use curl to invoke our new login API:
curl -v -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST http://localhost:3000/api/v1/sessions -d "{\"user\":{\"email\":\"user@example.com\",\"password\":\"secret\"}}"
If everything went fine, you should see the last line saying this (the auth_token will be different):
{"success":true,"info":"Logged in","data":{"auth_token":"JRYodzXgrLsk157ioYHf"}}
Test the logout
Using the auth_token that the API gave us back when we logged in and specifying the DELETE
verb we can reset the authentication token of the user, logging him out.
curl -v -H 'Content-Type: application/json' -H 'Accept: application/json' -X DELETE http://localhost:3000/api/v1/sessions/\?auth_token\=JRYodzXgrLsk157ioYHf
The result will be a nice message informing us that we are logged out.
{"success":true,"info":"Logged out","data":{}}
API Registrations Controller (register a new user)
The registrations controller extends the Devise one and has only one action, create. As you can see we skip the confirmation step with the method we added previously to the user model, we then save the new user and automatically log it in, returning the auth_token associated. Our user can then start using the API already logged in after the registration.
# file: app/controllers/api/v1/registrations_controller.rb
class Api::V1::RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token,
:if => Proc.new { |c| c.request.format == 'application/json' }
respond_to :json
def create
build_resource
resource.skip_confirmation!
if resource.save
sign_in resource
render :status => 200,
:json => { :success => true,
:info => "Registered",
:data => { :user => resource,
:auth_token => current_user.authentication_token } }
else
render :status => :unprocessable_entity,
:json => { :success => false,
:info => resource.errors,
:data => {} }
end
end
end
Add the register route to the API namespace:
# file: config/routes.rb
namespace :api do
namespace :v1 do
devise_scope :user do
post 'registrations' => 'registrations#create', :as => 'register'
post 'sessions' => 'sessions#create', :as => 'login'
delete 'sessions' => 'sessions#destroy', :as => 'logout'
end
end
end
Test the registration
Using the code we just added, we can now register new users from the JSON API. Try it out opening a command line and pasting this code.
curl -v -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST http://localhost:3000/api/v1/registrations -d "{\"user\":{\"email\":\"user1@example.com\",\"name\":\"anotheruser\",\"password\":\"secret\",\"password_confirmation\":\"secret\"}}"
If everything went fine, you should see the last line saying this (the id, dates and auth_token will be different):
{"success":true,"info":"Registered","data":{"user":{"created_at":"2012-10-08T19:57:20Z","email":"user1@example.com","id":3,"name":"anotheruser","updated_at":"2012-10-08T19:57:20Z"},"auth_token":"N8N5MPqFNdDz3G1jRsC9"}}
ActiveAdmin administrative interface
Just to ease the following steps, I wanted to introduce a simple administrative interface to our web application: let’s add the ActiveAdmin gem (with dependencies) and with few lines of code we’ll have a full featured admin area.
# file: Gemfile
gem 'activeadmin'
gem 'meta_search', '>= 1.1.0.pre' # activeadmin needs this if Rails >= 3.1
Don’t forget to launch the bundle install
command.
ActiveAdmin will then generate the configuration file and some migrations. You can find more information in the official documentation: www.activeadmin.info
bundle install
rails generate active_admin:install
rake db:migrate
Add a new file to the app/admin
folder to configure the users admin interface:
# file: app/admin/user.rb
ActiveAdmin.register User do
index do
column :name
column :email
column :current_sign_in_at
column :last_sign_in_at
column :sign_in_count
default_actions
end
filter :name
filter :email
form do |f|
f.inputs "User Details" do
f.input :name
f.input :email
f.input :password
f.input :password_confirmation
end
f.buttons
end
show do
attributes_table do
row :name
row :email
row :authentication_token
row :confirmed_at
row :current_sign_in_at
row :last_sign_in_at
row :sign_in_count
end
active_admin_comments
end
end
Launch the rails server
and go to http://localhost:3000/admin and login with the default ActiveAdmin credentials:
- User: admin@example.com
- Password: password
Bonus: Deploy to Heroku
In the next steps we will be building an Android app from scratch that will consume our API. To better test it on our smartphones, we will need the web app reachable from the web, what better place than Heroku?
This tutorial won’t delve to much on the details of deploying a Rails app on the service, but you can read more on the official documentation: devcenter.heroku.com.
Download the Heroku toolbelt and create an account if you don’t have one.
Let’s start creating a Git repository and pushing it to Heroku. Take note of the app’s name that Heroku will create for you (something.herokuapp.com).
rm public/index.html
git init
git add .
git commit -m "Initial commit"
heroku apps:create
git push heroku master
heroku run rake db:migrate
Now go to the address that Heroku created for your app and see if everything worked or not. More details on issues can be spotted with the heroku logs
command.
Part Two: The Android App
Proceed now to the second part of the tutorial, to learn how to build a fully featured Android App that can interact with the Ruby on Rails backend we just coded.
^Back to top