0

I understand this question has been asked before in various forms.

However I'm struggling with something that doesn't seem to be being solved in these answers. My logged in user is not persisting within the spec.

How are you supposed to replicate authentication/logged in users in a request spec?

Here's what I've tried, and what I'm doing. I'm using Auth0 as my authentication handler. I have a signin method that's called in the Auth0 Callback, so I've jerry-rigged a mock_auth endpoint for my tests to utilize a resource object.

This is my current set up and what I've done to try and replicate the login flow.

#/spec/requests/api/v1/account_spec.rb
RSpec.describe "API V1 Accounts", type: :request do
  # Factories.

  ...

  describe "PATCH update" do
    subject(:http_request) { patch endpoint, params: { account: account_params, format: :json } }

    # set some defaults
    let(:id) { account.id }
    let(:endpoint) { "/api/v1/accounts/#{id}" }
    let(:account_params) { {} }

    # Configure subdomain contstraint
    within_subdomain :api do
      before do |example|
        mock_login(resource) unless example.metadata[:skip_signin]
        http_request
      end

      context "when no resource is logged in", :skip_signin do
        # This spec passes fine, as it's skipping login.
        it_behaves_like "an unauthenticated private api request"
      end

      context "when there is no record to be found" do
        let(:id) { SecureRandom.uuid }
        let(:resource) { create(:user) }

        it "fails to access a record" do
          expect(response).to have_http_status(:not_found)
        end
      end

      xcontext "when the user has access permission" do
      end

    end
  end
end

-

# config/routes.rb
post "/auth/mock/:id", to: "auth#mock", as: :mock_login if Rails.env.test?

-

# auth_controller.rb
def mock
  return unless Rails.env.test?
  @resource = User.find_by(params[:id]
   signin(@resource)
end

def signin(resource)
  reset_session
  create_session(resource)
  after_signin_redirect_for(resource)
end

and I'm using this helper to call it from my request spec

module Helpers
  module Auth
    def mock_login(resource)
      post mock_login_path(resource.id)
    end
  end
end

RSpec.configure do |config|
  config.include Helpers::Auth, type: :request
end

So. By throwing around a bunch of debuggers and binding.pry I can see that my mock_login(resource) is being called successfully and at the end of the signin method, my helper signed_in? is true. Having successfully set a session.

The issue that I'm having now, is that this is not persisting in the feature spec when it's run in the before block, or in the it block.

before do |example|
  mock_login(resource) unless example.metadata[:skip_signin] # signed_in? == true!
  http_request # signed_in? == nil
end


module API
  module V1
    class AccountsController < APIController
      before_action :authenticate_resource!
      # ^ This is where the spec is failing to recognise the signed in resource from the mock_login method.
      before_action :set_account

      # PATCH /api/v1/accounts/:id
      def patch_update
        # Cancancan Authorization
        authorize! :update, @account 
        # handle patch
        ...
      end

      private

      def set_account
        binding.pry # We're never making it here.
        @account = Account.find_by(id: params[:id])
      end

      ...

    end
  end
end

def authenticate_resource!
  return true if signed_in?
  respond_to do |format|
    format.json { head(:unauthorized) }
  end
end

EDIT: A couple of changes to make it clearer what I'm asking.

James.Oliver
  • 100
  • 9
  • Its actually much easier than you think. https://github.com/omniauth/omniauth/wiki/integration-testing – max Mar 12 '19 at 13:27
  • Are you using cookies or what for authentication? What you seem to be missing is the session/request setup the requests are independent and there is no cookie jar as far as I know so one request is disconnected from the other, check the link that @max posted, it has something like `before { request.env... }` marking the user as authenticated for the next request. – Danilo Cabello Mar 12 '19 at 14:31
  • @max This is great for Oauth/Omniauth/Devise based Authentication. I'm using [Auth0][https://auth0.com/] which is slightly different. I don't think they have a "test mode" like Oauth, I certainly didn't see one while I was looking through their docs, but I'll look into it in more detail now I know exactly what I'm searching for. – James.Oliver Mar 12 '19 at 21:14
  • @DaniloCabello After the Auth0 handles the username/password they return the status to us to a callback, We set a session with an ID, Expiry and a User UUID. The mock_login method correctly sets this session, but the http_request method instantiates a new session, destroying the old one. I've figured out how to monkey patch this in the controller, by setting the session details from mock_login, to a cookie, then reading the cookie from a method only invoked when Rails.env.test? But this seems like a monkey patch that shouldn't need to be done. – James.Oliver Mar 12 '19 at 21:19
  • Sorry since you are doing the cookies nonsense I was thinking about the Auth0 setup for classical apps which does use OmniAuth. For an API I would skip cookies and go with a token based auth. Since its stateless you can simply mock authentication by passing an `Authentication` header with a JWT. – max Mar 12 '19 at 21:23
  • @max Does it give you access to the OmniAuth class though? If Auth0 Uses it under the hood, great, but I can't access the test/mock methods. And like I said in the question, I've successfully simulated the session creation to bypass Auth0. It's just creating a session that lasts the entirety of the request spec. or a single it block, rather than method to method. Also, I'm only using the cookie as a last resort, I'm hoping I can come up with something more stable, that's why I'm here. – James.Oliver Mar 12 '19 at 21:45
  • Auth0 does not use OmniAuth under the hood. The guide for classical Rails apps with Auth0 uses OmniAuth. It uses Auth0s OAuth endpoints just like FB, Twitter or any other provider. https://auth0.com/docs/quickstart/webapp/rails/01-login – max Mar 12 '19 at 21:51
  • They used to have a pretty decent guide for Rails APIs with [Knock](https://github.com/nsarno/knock) but it got replaced with a kind of crappy raw JWT guide. https://auth0.com/docs/quickstart/backend/rails/01-authorization – max Mar 12 '19 at 21:54
  • You can still probably setup Knock with Auth0 or setup a better token based auth system (the one in the guide is really bad). Handling JWT authentication is not really that complicated. You just decode the JWT token from the Authorization header and check if the claim corresponds to a record. – max Mar 12 '19 at 22:03
  • @max Thanks very much for this. It's been really helpful. I appreciate your input and your knowledge on the topic! – James.Oliver Mar 12 '19 at 22:10

0 Answers0