How to skip Devise authentication when using an API key?

18,636

Solution 1

One option no one has mentioned is to have a completely separate set of controllers for the API that do not inherit from ApplicationController.

I have seen the pattern used where API controllers live in files such as /app/controllers/api/v1/somethings.rb and are accessible via routes such as /api/v1/somethings. Each of the specific API controllers inherits from a base API controller that inherits from ActionController::Base, so does not include any of the filters defined on ApplicationController.

Solution 2

I know its been a while since this was asked, but thought I would throw in one more option that i've used in the past

class Api::ApplicationController < ApplicationController

skip_before_filter :authenticate_user!

This assumes that you have an application controller in your API directory that all of your api controllers inherit from. If not, you can just put the skip in each controller.

Solution 3

You can do this with your before_filter in your Controller.

Currently, you probably have something like:

class SomeController < ApplicationController
  before_filter :authenticate_user!
end

Instead of calling this, you can define a different method (ideally in ApplicationController)

class ApplicationController < ActionController::Base
  before_filter :authenticate_or_token

  private
  def authenticate_or_token
    if params[:api_key] == 1234
      @current_user = User.new(:admin => true, :any => "other", :required => "fields")
      return current_user
    end
    authenticate_user!
  end

I would recommend using a more robust method of authentication such as OAuth, but this should work for a simple 1-key based authentication.

Solution 4

An alternative to Gazler's would be to use an except:

class ApplicationController < ActionController::Base
  before_filter :authenticate_user!, except: :some_json_method

  def some_json_method
    render :nothing unless params[:api_key] == '1234'

    render :json
  end
end

This way you don't open your entire app to the key-holder (depending on your needs, whether you need that or not). If you need multiple methods opend to the key, you could probably also use something like:

class ApplicationController < ActionController::Base
  JSON_METHODS = [method_1, method2]
  before_filter :authenticate_user!, except: JSON_METHODS
  before_filter :authenticate_token, only: JSON_METHODS

  private
  def authenticate_token
    params[:api_key] == '1234'
  end
end
Share:
18,636
FilmiHero
Author by

FilmiHero

Updated on June 05, 2022

Comments

  • FilmiHero
    FilmiHero almost 2 years

    I'm using Devise on my application and would like to create a global API key that can access JSON data of anyone's account without having to log-in.

    For example, say my API Key is 1234 and I have two users who have created two different restaurants.

    • User 1 - Restaurant 1 (/restaurants/1)
    • User 2 - Restaurant 2 (/restaurants/2)

    And I open a brand new browser and haven't logged into anything and I pass into my URL .../restaurants/2.json?api_key=1234, I should be able to access the JSON data of that restaurant without having to log-in as User 2

    Whats the best way to do this?

    I've followed the Railscast #352 Securing an API so I'm able to access JSON stuff by passing in the API key but I have to log-in to see anything.

    Edit 1: Using CanCan

    I should mention that I'm also using CanCan for roles but not sure if that'll play any role (pun not intended) in this situation.

    Edit 2: Implimenting with API Versioning

    I followed the Railscast #350 and #352 which teach you how to create REST API Versioning and how to secure it with an API Key.

    Here's what my controllers/api/v1/restaurants/restaurants_controller.rb looks like:

    module Api
      module V1
        class RestaurantsController < ApplicationController
          before_filter :restrict_access
    
          respond_to :json
    
          def index
            respond_with Restaurant.all
          end
    
          def show
            respond_with Restaurant.find(params[:id])
          end
    
        private
    
          def restrict_access
            api_key = ApiKey.find_by_access_token(params[:api_key])
            head :unauthorized unless api_key        
          end
        end
      end
    end
    

    And my application_controller.rb still has the before_filter :authenticate_user! code in it.

    Solution

    I first followed the Railscast #350 on REST API Versioning and moved all my JSON API calls to /apps/api/v1/...

    Then, following Steve Jorgensen's solution below, made sure my API module inherited from ActionController::Base instead of ApplicationController so that it bypassed Devise's before_filter :authenticate_user! code within the ApplicationController.

    So, my Edit 2 code when from looking like this:

    module Api
      module V1
        class RestaurantsController < ApplicationController
        ...
    

    to

    module Api
      module V1
        #Replace 'ApplicationController' with 'ActionController::Base'
        class RestaurantsController < ActionController::Base
        ...