Tony Ennis
May 27, 2024
Devise header based token authentication
  • This is the approach we use for our hybrid mobile apps. It relies on a database-stored auth_token. This post doesn't cover expiring & refreshing the token or supporting multiple user sessions from different devices,

  • Setup

  • Add an auth_token method to the user model

  • # models/user.rb
    class User < ApplicationRecord
      has_secure_token :auth_token
    end
  • Set up a new devise (warden) auth strategy

  • # config/initializers/warden.rb
    class AuthTokenStrategy < Warden::Strategies::Base
      def valid?
        auth_token.present?
      end
    
      def authenticate!
        user = User.find_by(auth_token: auth_token)
    
        if user
          success!(user)
        else
          fail!('Invalid email or password')
        end
      end
    
      def store?
        false
      end
    
      private
    
      def auth_token
        env['HTTP_AUTHORIZATION'].to_s.remove('Bearer ')
      end
    end
    
    Warden::Strategies.add(:auth_token, AuthTokenStrategy)
  • Tell Devise to use the new strategy

  • Additionally, tell devise to skip session storage for this strategy

  • # initializers/devise.rb
    Devise.setup do |config|
    
      config.warden do |manager|
        manager.default_strategies(scope: :user).unshift :auth_token
      end
    
      config.skip_session_storage = [:http_auth,:auth_token]
    
    end
  • Create an API endpoint that will return the auth_token on successful sign in

  • # controllers/api/v1/users_controller.rb
    
    def sign_in
      user = User.find_by(email: params[:email])
    
      if user && user.valid_password?(params[:password])
        user.regenerate_auth_token if user.auth_token.blank?
        render json: {  
          user: { 
            email: user.email,
            auth_token: user.auth_token
          } 
        }, status: :ok
      else
        render json: { error: "Invalid password" }, status: :unauthorized
      end
    end
  • Usage

  • On the client, hit the login endpoint and get and store the auth_token. Then on subsequent requests include an authorization header with the token.

  • const response = await fetch(url, { 
      headers: {
        Authorization: `Bearer ${authToken}`,
        "X-Source": 'nifti-expo'
      }
    }); 
  • In your Rails app, use the auth helper methods as you normally would

  • class HomeController < ApplicationController
      before_action :authenticate_user!
    end

  • Website Page