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_webappDevise 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 userUncomment 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 => trueAdd 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_authenticatableAdd the before filter :ensure_authentication_token to the user model:
# file: app/models/user.rb
before_save :ensure_authentication_tokenAnd 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_tokenBonus: 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_meBonus: 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 => trueAdd 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_authenticatableWe 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
endWe 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:migrateAPI 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
endIn 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
endTest 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.saveClose 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\=JRYodzXgrLsk157ioYHfThe 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
endAdd 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
endTest 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.1Don’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:migrateAdd 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
endLaunch 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:migrateNow 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