Category Archives: REST

Introduction

Allowing users to login with multiple authentication providers brings great benefits but also results in some annoying edge cases. For example, what happens when they login with one provider, logout and then login with another? What happens when they try to login with one having already logged in with another?

Typically authentication systems have a User model which handles most of the authentication logic but having multiple logins forces you to correctly separate the concepts of an Identity and a User. An Identity is a particular authentication method which a user has used to identify themselves with your site whilst a User manages data which is directly related to your site itself.

In my previous article I explained how to integrate social signup on Rails using devise and omniauth gems. In this article I am going to explain how to manage multiple social logins as a single account. Main goal of this article is as follows:

1. User can sign up as a normal user and link his facebook/google account from his profile.
2. User can sign up with any social networking and manage his profile as a normal user.
3. In above both scenarios user can link/unlink his social accounts from his profile page.

Prerequisites

I am using the application which i have already created in my previous article

The present application is storing the user information and oauth information(UID & provider) in a single table called users as follows:

after_adding_ominiauht_columns

Authentications and Users

Now it’s time to separate the user information & oauth information. To do this I am going to add another table called authentications which will store all oauth information. And I am removing oauth information from user table. First create a model to store oauth data from console(application root directory):

 rails g model authentication provider:string uid:string user_id:integer

It will create required files for the table authentication. Modify the authentication model as follows:

 #File app/models/authentication.rb
 class Authentication < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :user_id, :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider
  def provider_name
     provider.titleize
  end
end

I have added the user association and added a instance method which will return provider name in camelcase. Here we’re modeling very simple relationships and making sure that the Authentication has both a provider (e.g. “twitter” or “facebook”) and a uid (i.e. the external service ID). Next up, we’ll wire these models into our controller to create a real sign in process. Now add the association in user as well which means a user can have zero or many authentications.

 #File app/models/user.rb
 class User < ActiveRecord::Base
  .
  .
  .
  has_many :authentications # Add this single line
  .
  .
 end

Adding association from model level was completed. It’s time to add the authentication table and remove oauth information from user table. Now the authentication migration looks like this:

 #File db/migrate/20150907085047_create_authentications.rb
 class CreateAuthentications < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.string :provider
      t.string :uid
      t.integer :user_id

      t.timestamps
    end
  end
end

To remove oauth info from user table create a migration from console:

 rails g migration remove_provider_fileds_from_user

It will create one migration file under db/migrate. Open the file and add the following code:

 # File db/migrate/20150907085601_remove_provider_fileds_from_user.rb
 class RemoveProviderFiledsFromUser < ActiveRecord::Migration
  def change
    remove_column :users, :provider
    remove_column :users, :uid
  end
 end

Now we have done adding required migrations to modify database tables. Run migrations from console:

 rake db:migrate

It will create a table called authentications and removed the uid and provider columns form user table. The following is the domain model for current requirement.

oauth_user_domain_model

Signing Up/In

One of the nice things about external authentication is you can collapse the sign up and sign in process into a single step. What we’ll do here is:

1. When a user signs in, look for existing Authorizations for that external account.
2. Create a user if no authorization is found.
3. Add an authorization to an existing user if the user is already logged in.

Let’s work backwards for this functionality by adding the code we want to have to the controller. Create a controller called authentications from console:

  rails g controller authentications
 

It will create a controller and a directory under app/views. Add the following code to newly created controller:

 # File app/controllers/authentication_controller.rb
 class AuthenticationsController < ApplicationController
  def index
    @authentications = current_user.authentications if current_user
  end

  def create
    omniauth = request.env["omniauth.auth"]
    authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
    if authentication
      flash[:notice] = "Signed in successfully."
      sign_in_and_redirect(:user, authentication.user)
    elsif current_user
      current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
      flash[:notice] = "Authentication successful."
      redirect_to authentications_url
    else
      user = User.new
      user.apply_omniauth(omniauth)
      if user.save
        flash[:notice] = "Signed in successfully."
        sign_in_and_redirect(:user, user)
      else
        session[:omniauth] = omniauth.except('extra')
        redirect_to new_user_registration_url
      end
    end
  end

  def destroy
    @authentication = current_user.authentications.find(params[:id])
    @authentication.destroy
    flash[:notice] = "Successfully destroyed authentication."
    redirect_to authentications_url
  end

Now Add the following code to existing user model which will be used for registration process:

  # File app/models/user.rb
  class User < ActiveRecord::Base
   .
   .
   # Add the following methods
  def apply_omniauth(omniauth)
    authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
  end

  def password_required?
    (authentications.empty? || !password.blank?) && super
  end

  def existing_auth_providers
    ps = self.authentications.all

    if ps.size > 0
      return ps.map(&:provider)
    else
      return []
    end
  end
   .
   .
  end
 

we have created the authentications controller and updated the code in both authentications controller & user model. Add an index view for authentications controller which will show you the number of authentications of a user.

 #File app/views/authetications/index.html.erb
 <% "Sign In Options" %>

<% if @authentications %>
  <% unless @authentications.empty? %>
    <p><strong>You have linked these services with your account:</strong></p>
    <div class="authentications">
      <% for authentication in @authentications %>
        <div class="authentication">
          <%= image_tag "#{authentication.provider}_icon.png", size: "32x32"%>
          <div class="provider"><%= authentication.provider_name %></div>
          <div class="uid"><%= authentication.uid %></div>
          <%= link_to "X", authentication, :confirm => 'Are you sure you want to remove this authentication option?', :method => :delete, :class => "remove" %>
        </div>
      <% end %>
      <div class="clear"></div>
    </div>
  <% end %>
<% else %>
  <p><strong>Sign in through one of these services:</strong></p>
<% end %>

<p><strong>Add another service to sign in with:</strong></p>
  <%- current_user.class.omniauth_providers.each do |provider| %>
    <%- if !current_user.existing_auth_providers.include?(provider.to_s) %>
      <%= link_to omniauth_authorize_path(current_user.class, provider) do %>
          <%= image_tag "#{provider.to_s}_icon.png", size: "32x32" %>
      <% end %>
    <% end %>
  <% end -%>

<div class="clear"></div>

<% unless user_signed_in? %>
  <p>
    <strong>Don't use these services?</strong>
    <%= link_to "Sign up", new_user_registration_path %> or
    <%= link_to "sign in", new_user_session_path %> with a password.
  </p>
<% end %>

Now it’s time to override the devise registration controller. Following are the next steps:
1. create a registration controller and update the code.
2. copy the devise registrations views(create, edit) and change the code.
3. Tell the devise routes to use our registrations controller instead of it’s own controller.
4. Update the callback controller logic same as authentication controller.

step1: create a registration controller & views

From the console create controller called registrations:

  rails g controller registrations
 [/source]

 Add the following logic to the controller:
 #File app/controllers/registrations_controller.rb
 
  class RegistrationsController < Devise::RegistrationsController
  def create
    super
    session[:omniauth] = nil unless @user.new_record?
  end

  private

  def build_resource(*args)
    super
    if session[:omniauth]
      @user.apply_omniauth(session[:omniauth])
      @user.valid?
    end
  end
end
 

Next copy devise registration views from app/views/devise/registrations to app/views/registrations and update the code as follows.

  #File app/views/registrations/new.html.erb
  <div class="border-form-div">
<h2>Sign up</h2>

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <p><%= f.label :email %><br />
  <%= f.text_field :email %></p>

<% if @user.password_required? %>
  <p><%= f.label :password %><br />
  <%= f.password_field :password %></p>

  <p><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></p>
<% end %>

  <p style="text-align: center;"><%= f.submit "Sign up", :class => 'btn_login' %></p>
<% end %>

<%= render :partial => "devise/shared/links" %>
</div>
 
 #File app/views/registrations/edit.html.erb
 <div class="border-form-div">

<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put, :class => "edit_user_form"}) do |f| %>
  <%= devise_error_messages! %>

  <p><%= f.label :email %><br />
  <%= f.text_field :email %></p>

  <p><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
  <%= f.password_field :password %></p>

  <p><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></p>

  <p><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
  <%= f.password_field :current_password %></p>

  <p style="text-align: center;"><%= f.submit "Update", {:class => "btn_login"} %></p>
<% end %>

<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>

<%= link_to "Back", :back %>
</div>

step2: Update the routes.rb

We have to tell the devise engine to use our custom registration controller. To do that add update the routes file as follows:

 #File config/routes.rb

 Rails.application.routes.draw do
 .
 .
  #update the existing devise route as follows
  devise_for :users, :controllers => {:registrations => 'registrations', :omniauth_callbacks =>  "callbacks"}

 #Add the following line
  post '/auth/:provider/callback' => 'authentications#create'
 .
 .
 end

Step3: Update the callback controller as follows:

  class CallbacksController < Devise::OmniauthCallbacksController
  def all
    omniauth = request.env["omniauth.auth"]
    authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
    if authentication
      flash[:notice] = "Signed in successfully."
      sign_in_and_redirect(:user, authentication.user)
    elsif current_user
      current_user.authentications.create!(:provider => omniauth['provider'], :uid => omniauth['uid'])
      flash[:notice] = "Authentication successful."
      redirect_to authentications_url
    else
      user = User.new
      user.apply_omniauth(omniauth)
      if user.save
        flash[:notice] = "Signed in successfully."
        sign_in_and_redirect(:user, user)
      else
        session[:omniauth] = omniauth.except('extra')
        redirect_to new_user_registration_url
      end
    end
  end
  alias_method :facebook, :all
end
 

previously I have created a separate method for each provider. Now the functionality is almost same for all providers. I have created a common method called ‘all’ and created an alias for facebook provider. you can add the aliases for other providers as well. Here I am using only facebook signup.

All the coding part was done. Now we are going to test the application. Start the rails server from console and try to access it from browser “http://localhost:3000”. Following is the output. I have added some styles for layout as well.

link_social_login

Now a user can sign in using any of their social accounts and a User will automatically be fetched or created. The signup page look like this:

link_social_new_user

Here we have two scenarios:

1. Signup as a normal user and link the other social accounts.
2. Signup with any social network and edit his profile like a normal user.

Scenario one:

Now i am going to signup as a normal user by providing required information:

new_user_signup

After signup it will redirect to the application homepage. here it is products list page. we have to test the authentications of the newly created user. Try to access the link “http://localhost:3000/authentications” from browser. It will look like as follows:

authentications_index_page

From here the newly created normal user don’t have any authentications. He can link the available social accounts from here. If user clicks the facebook icon it will got he facebook login page. once user authenticated from facebook our backend logic will automatically link the facebook authentication to the already logged in user. now the page look like this:

authentication_links

Note: I have removed the UID from the screen for security reasons.

Now the user can login using his normal user account or login with his facebook account. But the user entity will same for both logins. Means this user have one authentication. If user want to unlink just click on the ‘x’ mark it will simple remove the facebook authentication from database.

Scenario two:

User can login with any provider without registering as a new user. Say for example a user want to login with his facebook account without providing any information like email.. In our application we have validation for email to create a new user. If user is trying to login with his FB account first time it will ask for an email after authenticated from FB. The screen will look like this:

first_time_social_login

Once user enter the email it will validate & create a new user and link that FB authentication with the user. Like this User can link available social services after login.

We hope this was useful!

Introduction

In this 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 my previous article I have explained how to build a rails application with registration & social login here is the link http://blogs.nbostech.com/2015/08/loginregistration-social-signup-using-ruby-on-rails/ . I am going to continue this article with the application which I have created in the above post. Which means I am going to create API for that application.

Latest version of devise gem does not support token_authenticatable module. To work with token_authenticatable module i am using another gem called “devise-token_authenticatable”. Add this gem to your gemfile.

 gem "devise-token_authenticatable"

After adding this gem run following command under application root to update your gems.

 bundle install

After running bundle install 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
 

Add the :token_authenticatable to the devise modules in the user model:

    class User < ActiveRecord::Base
      # Include default devise modules. Others available are:
      # :confirmable, :lockable, :timeoutable and :omniauthable
      devise :database_authenticatable, :registerable, :token_authenticatable,
             :recoverable, :rememberable, :trackable, :validatable ,
             :omniauthable, :omniauth_providers => [:facebook]
  

Now the configuration part was done. Next we need to add a column to user table by creating a migration file.

   rails g migration add_authentication_token_column_to_users
  

After creating the migration open that file add the following code:

    class AddAuthenticationTokenColumnToUsers < ActiveRecord::Migration
      def change
  	  add_column :users, :authentication_token, :string
  	  add_index :users, :authentication_token, :unique => true
      end
    end
  

Now run the migration to add the new column to users table in database:

     rake db:migrate
   

After running above migration user model looks like this:

token_based_auth

Now we are ready to configure our application to work with token based authentication. First add the following to user model after devise configuration:

     before_save :ensure_authentication_token
   

Next add the following methods to user model at the end of all methods.

     def ensure_authentication_token
        if authentication_token.blank?
          self.authentication_token = generate_authentication_token
        end
     end

    private

     def generate_authentication_token
       loop do
         token = Devise.friendly_token
         break token unless User.find_by(authentication_token: token)
       end
     end
   

Above code will ensure that each and every user must have a auth token every time a user created or existing user updated. Now the auth token setup was done.

API Sessions Controller (login and logout)

Let’s start coding the sessions controller that will be used by API 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/v0/ folder. Create a directory ‘api’ under controllers and create a directory called ‘v0’ under controllers/api/

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/v0/sessions_controller.rb
  class Api::V0::SessionsController < Devise::SessionsController
    skip_before_filter :verify_authenticity_token,
                       :if => Proc.new { |c| c.request.format == 'application/json' }
    skip_before_filter :verify_signed_out_user                          

    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 need to add our namespace and the two login and logout routes.

    # file: config/routes.rb
    namespace :api do
      namespace :v0 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(:first_name => 'test', :last_name => 'user', :email => 'user@example.com',    :password => 'secret1234', :password_confirmation => 'secret1234')

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/v0/sessions -d "{\"user\":{\"email\":\"user@example.com\",\"password\":\"secret1234\"}}"
 

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/v0/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. Here we can 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.

  class Api::V0::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(sign_up_params)
      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

    def sign_up_params
      params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name)
    end
  end
 

Add the register route to the API namespace:

    # file: config/routes.rb
    namespace :api do
      namespace :v0 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/v0/registrations -d "{\"user\":{\"email\":\"user1@example.com\",\"first_name\":\"another\", \"last_name\":\"user\",\"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":{"id":3,"email":"user1@example.com","created_at":"2015-09-01T12:12:50.866Z","updated_at":"2015-09-01T12:12:50.966Z","provider":null,"uid":null,"first_name":"another","last_name":"user","authentication_token":"3XC3BJpNvKHDoXTZb4zJ"},"auth_token":"3XC3BJpNvKHDoXTZb4zJ"}}
 

You now have a modern user authentication system for your app, where users can sign in & sign up using an email address and a password using API.

We hope this was useful!