Using Auth0 for Authentication in Your Rails App

I have to be very upfront with you, I hate developing authentication system by now. After rolling out enough authentication system you, maybe you will feel the same way with me. You may say to me, “Galih, it’s easy. Just use Devise gem and you are all set for life”. I agree with you mate, Devise is a brilliant solution, it provides you with a lot of stuffs out of the gate, used by many so that you can guarantee any problem that you may have will have already be experienced by others, and in case you encounter a brand-new problem, Devise has lots of contributors that may come to rescue you. But the honeymoon phase is over the minute you want to customize anything related to your authentication system. Do not get me wrong, Devise is a well-documented library, but you definitely have to go through the documentations for any customization. And when the day comes when you have to extend your authentication system, like adding stuffs in to tie with the authentication, it will not be fun.

You may go and choose the solutions on the other end of spectrum, the simpler authentication gems, the gems worth mentioning are: Authlogic, Clearance, Sorcery (Full disclosure though, I have never used this gems in production). But then again, as simpler as they are compared to Devise, you will still have to abide to their DSL (Domain Specific Language). And because they are more lightweight than Devise, you will have to write more lines of code to have the complete authentication system features. More lines of code you have to write (even using methods provided by those gems) means more lines of code that have the potential to cause bugs and more lines to cover in your test suite. So that is the trade-off between using a full-blow system like Devise and the simpler solutions.

In your case, it may be that Devise is the best solution, maybe because you are being chased by deadline. Or that those simpler gems may provide you with the best solution because you value the comfort of having methods provided to you out of the gates. But if you want to build an authentication system that lasts, easy to maintain, customize, and extend as you wish, then hopefully this post will be very useful to you.

Painless Authentication With Auth0

Auth0 is an authentication system provider that will basically give you a lot of functionalities that you expect an authentication system should have like user database, forgot and reset password feature, and of course registering and logging in users. All you have to do is setup a configuration (called as “client application”) for your app in your Auth0 dashboard and then integrate that configuration with your app. So basically your rails app will send data to Auth0 to be process and stored. In return, Auth0 sends you a JSON response that you can use and/or in your database. On the UI side, Auth0 does provide you a UI form (they call it Lock) that you can embed easily using JS, so we do not have to worry about it. Enough of this intro, let’s start coding.

This post assumes that you already have set up a client application in your Auth0 dashboard and get the Domain, Client ID, and Client Secret. We are going to need those 3 things. Before we begin the fun stuff, let’s start by doing the mundane, but necessary tasks. First stop, our gemfile:

gem 'omniauth'
gem 'omniauth-auth0'
gem 'figaro' # use this or dotenv, but this post assumes you use figaro

We use omniauth to make our life easier, but do not forget to setup the proper initializer, as like everytime we use omniauth with any providers:

config/initializers/auth0.rb

Rails.application.config.middleware.use OmniAuth::Builder do
  provider(
    :auth0,
    ENV['AUTH0_CLIENT_ID'],
    ENV['AUTH0_CLIENT_SEC'],
    ENV['AUTH0_CLIENT_DOM'],
    callback_path: "/auth/auth0/callback" # This is up to you, but should match this with the one you set on the Auth0 dashboard and also the one you set up on the routes.rb
  )
end

Let’s setup the route:

config/routes.rb

# IMPORTANT: match the callback path with the one you set on Auth0 clien application and initializers
get '/auth/auth0/callback' => 'auth0#callback' 
delete '/auth/auth0/logout' => 'auth0#logout'
get '/auth/failure' => 'auth0#failure'

These are the ONLY routes we need for actions that we will code in our app, because the rest of functionalities like reset password and etc, is handled by Auth0. Cool, right?

This post assumes you use a model called User like this:

class User < ActiveRecord::Base
  # No more cross-contamination by Devise, yay!
  
  # For the sake of brevity, the table users have this for the migration:
  # create_table :users do |t|
  #   t.string :email, null: false
  #   t.string :name, null: false
  # end
end

Notice that we do not need password columns because we do not store password used by the user in our app, it is stored by Auth0. +1 point for security. You can easily extend that users table if you wish, but we will keep it simple for this post.

Integrating Auth0 registration to your Rails app

First, let’s prepare the user registration. It is up to you how or where you want to serve the Auth0 lock page (the registration/login form), but for the sake of brevity let’s assume you will put it in the base layout.

app/views/application.html.erb

<!-- Redacted -->
<body>
  <!-- Header, Navbar -->
  <%= render 'layouts/classic_navbar' %>
  <!-- Wrapper, Content/sec -->
  <%= render 'layouts/messages' %>

  <div class="auth0-pages-js">
    <!-- Login/Signup link -->
    <%= link_to 'Sign Up', '#', class: "auth0-signup" %>
  </div>
    
  <%= yield %>

</body>
<!-- Redacted -->

Notice that the link does not lead to anywhere but when user clicks it, we want the app to serve the lock page in modal form. Now let’s write the js script to process the click event to serve the modal:

app/assets/javascripts/auth_lock.js

$('.auth0-pages-js').ready(
  
  var signup_lock = new Auth0Lock(
    '<%= ENV['AUTH0_CLIENT_ID'] %>', 
    '<%= ENV['AUTH0_CLIENT_DOM'] %>', {
      closable: true, autoclose: true,
      allowLogin: false,
      additionalSignUpFields: [{
        name: 'name',
        placeholder: 'your full name',
        icon: 'icon-to-be-displayed.png'
      }],
      theme: {
        logo: 'logo-to-be-displayed.png',
        primaryColor: '#39D5FF',
        labeledSubmitButton: false
      },
      languageDictionary: {
        emailInputPlaceholder: "email address",
        passwordInputPlaceholder: "your password",
        signUpLabel: "Registration",
        signUpWithLabel: "Registration with %s",
        title: "Registration"
      },
      auth: {
        redirectUrl: '<%= ENV['AUTH0_CALLBACK_URL'] %>',
        responseType: 'code',
        params: {
          scope: 'openid email' 
        }
      }
  });

  $('.auth0-signup').click(function (event) {
    // This line below displays the var signup_lock declared above
    signup_lock.show()
    event.preventDefault(); // Prevent link from following its href
  });

);

And just like that, we are done with the UI for registration step. Time to work on the backend.

app/controllers/authentication_controller.rb

class AuthenticationController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :callback

  def callback
    # This stores all the user information that came from Auth0
    # and the 3rd party identity providers (fb, twtr, gogl)
    auth0_response = request.env['omniauth.auth']
    
    # Process the auth hash to get the user
    user = Auth0Authentication.new(auth0_response).run!
    cookies[:current_user_id] = user.id

    flash.notice = 'Signed in!'
    # Redirect to the URL you want after successfull auth
    redirect_to home_path
  end

  def failure
    # show a failure page or redirect to an error page
    flash[:error] = request.params['message'].to_sentence
  end
end

Notice that the user variable is declared by running a class called ‘Auth0Authenitcation’, which is a service class that will process the response from Auth0 and return a user object, whether it is a newly registered user or an already existing user on your database. After that, we assign the ID of that user object to the session.

app/services/auth0_authentication.rb

class Auth0Authentication
  def initialize(auth)
    @auth = auth
    @info = auth[:info]
    @raw = auth[:extra][:raw_info]
    @metadata = @raw[:user_metadata] if @raw[:user_metadata]
    @identities = @raw[:identities] if @raw[:identities]
    @provider = @identities.first[:provider]
    if @metadata
      @name = @metadata[:name]
    else
      @name = @raw[:name]
    end
  end

  def run!
    create_user(email: email)
  end

  private

  def create_user(email:)
    # Now create user in our database, so that we can refer the user inside our app.
    User.create(email: email) do |user|
      user.name       = @name
      user.avatar     = @raw[:picture] if @raw[:picture]
      user.auth_with  = @provider
      # And extend this as you wish
      
    # If for example you already have password columns in your existing database
      # and it is required to be filled, then just stub it with secure random value
      # user.password   = Devise.friendly_token[0,20]
      # user.password_confirmation = user.password      
    end
  end
end

Add Auth0 login to your rails app

Now that we are able to accept registration using Auth0 in our rails app, the next step is to enable the login functionality. Let’s start on the front-end first:

app/layouts/application.html.erb

<!-- Redacted -->
<body>
  <!-- Header, Navbar -->
  <%= render 'layouts/classic_navbar' %>
  <!-- Wrapper, Content/sec -->
  <%= render 'layouts/messages' %>

  <div class="auth0-pages-js">
    <!-- Login/Signup link -->
    <%= link_to 'Sign Up', '#', class: "auth0-signup" %>
    <%= link_to 'Log In', '#', class: "auth0-login" %>
  </div>
    
  <%= yield %>

</body>
<!-- Redacted -->

Like before, add the layout then add the javascript to trigger the lock modal.

app/assets/javascripts/auth_lock.js

$('.auth0-pages-js').ready(
  // redacted

  var login_lock = new Auth0Lock('<%= ENV['AUTH0_CLIENT_ID'] %>', '<%= ENV['AUTH0_CLIENT_DOM'] %>', {
    closable: true, autoclose: true,
    allowSignUp: false,
    initialScreen: 'login',
    theme: {
      logo: 'your-logo.png',
      labeledSubmitButton: false
    },
    languageDictionary: {
      emailInputPlaceholder: "your email address",
      passwordInputPlaceholder: "your password",
      title: "Authentication"
    },
    auth: {
      redirectUrl: '<%= ENV['AUTH0_CALLBACK_URL'] %>',
      responseType: 'code',
      params: {
        scope: 'openid email' // Learn about scopes: //auth0.com/docs/scopes
      }
    }
  });

  $('.auth0-login').click(function (event) {
    login_lock.show()
    event.preventDefault(); // Prevent link from following its href
  });
  
);

Full disclosure, you can actually combine the login and registration into one form by setting the line written above to this:

    allowSignUp: true,

Moving on, there is actually no work needed to be done on the controller, but we do have to adjust the service class called on the callback action of the controller:

app/services/auth0_authentication.rb

class Auth0Authentication
  # redacted
  
  def run
    # try to find exisiting user
    email = @info[:email]|| @raw[:email]
    user = User.find_by(email: email)
    
    if user.nil?
      # Only create new user on the database when we do not find an
      # existing one with that email address
      user = create_user(email: email)
    else
      # User is found, so we should put the logic to retrieve and update
      # the data stored in our database so that it matches with the one stored
      # by Auth0
      # This bit is optional, but sometimes necessary particularly when you
      # enable multiple 3rd party identity providers. This is so that your user record
      # on the database gets updated everytime the user logs in.
      refresh_user_attributes(user: user, email: email)
    end
    
    user
  end
  
  private
  
  --redacted--
    
  def refresh_user_attributes(user:, email:)
    user.email ||= email
    user.name = @name

    user.save
  end  
end

And just like that, you have built a working authentication that is both extensible and simpler.

Oops, I speak to soon. We have forgotten about something improtant. Have you noticed something weird about our authentication system? We do not have logout feature yet. So let’s add to the authentication controller:

app/controllers/authentication_controller.rb

  def logout
    session[:userinfo] = nil
    session[:current_user_id] = nil

    url = '<path_in_your_app_after_logging_out>'

    flash.notice = 'Successfully logging out!'
    redirect_to "https://<your_auth0_subdomain>.auth0.com/v2/logout?returnTo=#{url}"
  end

This is important part of the authentication system and should not be forgotten, ever. What idiots ever forget logout feature when coding authentication system? Not us, right?

Securing our authentication system

Everything so far is working fine, however there is actually some security measures that we still have to code. The reason I do not put it earlier is to give this security measure more emphasis and focus. The most important security problem with authentication system is Session HIjacking. Basically, session hijacking involves changing the session variable to pose and act as another user. And in the examples above, actually we have opened ourselves open to this problem with this line of code:

class AuthenticationController < ApplicationController
  skip_before_action :verify_authenticity_token, only: :callback

  def callback
    --redacted
    
    # This line below here
    cookies[:current_user_id] = user.id
    
    --redacted
  end
end

That line of code stores the user_id retrieved from the database to the cookies with the key :current_user_id. Now, for someone to do a session hijacking in our app, would be as easy as tinkering with cookies stored on that person’s browser and change the value from his/her user_id to another user’s id. Even if the hijacker do not know the exact id of other users, hijacker can just predict it and then do a trial-and-error until he/she got it right. Blimey, right?

However, no worry, as long as you are rails developer, there will always be an easy way to do anything, even preventing session hijacking. Just change the problematic line above to this:

cookies.signed[:current_user_id] = user.id

Or, as we are after all using rails, there is a better way:

session[:current_user_id] = user.id

The difference between using the session token and cookies (without the .signed) is that the information you store will be automatically encrypted by rails using your application secure token. So the hijacker won’t be able to see the current_user_id is equal to 42, for example, in plain view. The only way for the hijacker to know the current_user_id is by obtaining the app’s secure token and decrypt the encrypted cookies variable.

I know, I know, you may be wondering, “why did he not tell us earlier to use session token to save us all the hassles?” The thing is I believe that you learn more when things go wrong. By showing (or leading) you the wrong way and its dangerous consequence, hopefully you will stay away from it.

It is also recommended to enforce ssl, especially as ssl certificate becoming more affordable. Adding this line in your production.rb (or application.rb, if you want to enforce it in all environments) :

config.force_ssl = true

If you want to understand more about cookies and session, here is a great article that explains those topics in depth.

Now, another session-related security pitfall is called ‘Session Fixation’. Basically it enables the attacker to use the same session with the victim in accessing the app. And as always, a simple line of code greatly reduce the possibility of this happening in your app:

class AuthenticationController < ApplicationController

  def callback
    --redacted
    
    user = Auth0Authentication.new(auth0_response).run!
    reset_session if @user# this line here
  session[:current_user_id] = user.id
  
    --redacted
  end
end

You can read more about this step in the rails guide. Oh do not forget to add that line in the logout action as well:

  def logout
    reset_session # This line here
    url = '<your_app_after_logging_out_page>'

    flash.notice = 'Successfully logging out!'
    redirect_to "https://<your_auth0_subdomain>.auth0.com/v2/logout?returnTo=#{url}"    
  end

Now that we are done, let’s finish with flourish. One of the convention in rails app authentication system is using current_user to refer to user that is currently logged in. So let’s write the methods related to that:

module CurrentUser
  extend ActiveSupport::Concern
  included do
    helper_method :current_user, :current_user?, :user_signed_in?, :authenticate_user!
  end

  def current_user
    User.find(id: session[:current_user_id])
  end

  def user_signed_in?
    session.key?(:current_user_id) && session[:current_user_id].present?
  end

  def current_user?(user)
    return true if current_user == user
    false
  end

  def authenticate_user!
    redirect_to root_path unless session[:current_user_id].present?
  end
end

And do not forget to include this:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  --redacted
  
  include CurrentUser 
end

It is the job for the developers to research and choose the best solution that matches the problem. Should your case is to build authentication system that is modular, easy to maintain and extend, hopefully this long post is able you to get started.