RoR, How can I unit test controllers using RSPEC which are protected by JWT authentication system?

64 Views Asked by At

I'm trying to unit test RoR api controllers using rspec-rails gem.
All my controllers are implemented this ProtectedController class which intercept all request and validate the authorization header

class V1::ProtectedController < V1::BaseController
  include JsonWebTokenAuthentication

  attr_reader :current_user
  before_action :validate_token!
  # Check if authorize has been called (Pundit gem)
  after_action :verify_authorized

  private

  def validate_token!
    authorization_header = request.headers['Authorization'] || ''
    puts "header: #{request.headers['Authorization']}"
    jwt = JsonWebToken.new(authorization_header.gsub('Bearer ', ''))
    if payload = jwt.verify
      account = Account.find_by(id: payload[:user][:id])
      if account.nil? || account.token_checksum == payload[:user][:token_checksum]
        # If no account found still authorize because it is likely an app token
        @current_user = Account.from_jwt_data(payload[:user])
      end
    end
    return if @current_user

    raise JsonWebTokenAuthentication::Unauthorized, 'invalid token'
  end
end

In order to simulate this authentication system for testing I create this AuthHelper module which will return an admin JWT ( later I can create other logins for other roles or I can pass login creden to this function)

module AuthHelper
  include Rack::Test::Methods

  def app
    Rails.application # This assumes you're using a Rails application
  end
  def admin_login
    params = {
      grant_type: 'password',
      email: '[email protected]',
      password: 'preci666'
    }
    post '/api/v1/auth/token', params
    expect(last_response.status).to eq(200)

    auth_response = JSON.parse(last_response.body)
    @access_token = auth_response['access_token']
  end

end

Now all what I need to do is to pass the JWT in all my requests. which I did in for example my workers_controller_spec as shown bellow

require 'rails_helper'

RSpec.describe V1::WorkersController, type: :request do
  include AuthHelper

  before { 
    admin_login 
    auth = "Bearer #{@access_token}"
  }


  describe "GET #index" do
    it 'assigns @workers' do
      worker1 = FactoryBot.create(:worker)
      worker2 = FactoryBot.create(:worker)
      #request.headers['Authorization'] = auth
      auth = "Bearer #{@access_token}"
      #request.headers['Authorization'] = auth
      get v1_workers_path , headers: {
        Authorization: auth
      }

      expect(response).to have_http_status(200)
    end
  end
end

For me all looks correct like this but not for rspec, the response status is always 401. and the weird part that when I debugg the request.headers['Authorization'] in my protected controller I found it empty and in request.headers.inspect I can see my Authorization header :/
When I tried to pass the header as in the commented part #request.headers['Authorization'] = auth I get this error

1) V1::WorkersController GET #index assigns @workers
 Failure/Error: request.headers['Authorization'] = auth
 
 ArgumentError:
   wrong number of arguments (given 0, expected 1..2)

puts "header: #{request.headers['Authorization']}" in my protected controller returns

header: 

puts "request: #{request.inspect}" returns

header: request: #<ActionDispatch::Request GET 
"http://example.org/api/v1/workers? 
 headers[Authorization]=Bearer+eyJhbGciOiJSUzI1NiJ9.eyJp..." for 
 127.0.0.1>

ruby version : 2.7.7 rails version: 7.0.4.2 rspec-rails version: 6.0.1

Any one can help me to find a solution? or is there more proper way to simuate my authentication system and return the current_user?

0

There are 0 best solutions below