Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do request spec for JWT authenticate app using RSpec

I have a Rails 5 API only app and using knock to do JWT authenticate.

After complete the model and model spec, I start to do the request spec.

But I have no idea how to complete the authentication inside the request spec in the right way,

My users controller,

module V1
  class UsersController < ApplicationController
    before_action :authenticate_user, except: [:create]
  end
end

Application controller,

class ApplicationController < ActionController::API
  include Knock::Authenticable
  include ActionController::Serialization
end

My stupidest solution (call the get token request to get the JWT before the rest request),

context 'when the request contains an authentication header' do
  it 'should return the user info' do
    user  = create(:user)
    post '/user_token', params: {"auth": {"email": user.email, "password": user.password }}
    body = response.body
    puts body # {"jwt":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0ODgxMDgxMDYsInN1YiI6MX0.GDBHPzbivclJfwSTswXhDkV0TCFCybJFDrjBnLIfN3Q"}
    # use the retrieved JWT for future requests
  end
end

Any advice is appreciated.

like image 982
X.Creates Avatar asked Feb 19 '17 11:02

X.Creates


3 Answers

With the help of Lorem's answer, I was able to implement something similar for my request spec. Sharing it here for others to see an alternate implementation.

# spec/requests/locations_spec.rb
require 'rails_helper'

RSpec.describe 'Locations API' do
  let!(:user) { create(:user) }
  let!(:locations) { create_list(:location, 10, user_id: user.id) }

  describe 'GET /locations' do
    it 'reponds with invalid request without JWT' do
      get '/locations'
      expect(response).to have_http_status(401)
      expect(response.body).to match(/Invalid Request/)
    end

    it 'responds with JSON with JWT' do
      jwt = confirm_and_login_user(user)
      get '/locations', headers: { "Authorization" => "Bearer #{jwt}" }
      expect(response).to have_http_status(200)
      expect(json.size).to eq(10)
    end
  end
end

confirm_and_login_user(user) is defined in a request_spec_helper which is included as a module in rails_helper.rb:

# spec/support/request_spec_helper.rb

module RequestSpecHelper
  def json
    JSON.parse(response.body)
  end

  def confirm_and_login_user(user)
    get '/users/confirm', params: {token: user.confirmation_token}
    post '/users/login', params: {email: user.email, password: 'password'}
    return json['auth_token']
  end
end

I'm using the jwt gem for generating my tokens as described in this SitePoint tutorial (https://www.sitepoint.com/introduction-to-using-jwt-in-rails/)

like image 122
quicklikerabbit Avatar answered Oct 20 '22 15:10

quicklikerabbit


  def authenticated_header(user)
    token = Knock::AuthToken.new(payload: { sub: user.id }).token
    { 'Authorization': "Bearer #{token}" }
  end

  describe 'GET /users?me=true' do
    URL = '/v1/users?me=true'
    AUTH_URL = '/user_token'

    context 'when the request with NO authentication header' do
      it 'should return unauth for retrieve current user info before login' do
        get URL
        expect(response).to have_http_status(:unauthorized)
      end
    end

    context 'when the request contains an authentication header' do
      it 'should return the user info' do
        user  = create(:user)

        get URL, headers: authenticated_header(user)
        puts response.body
      end
    end
  end
like image 36
X.Creates Avatar answered Oct 20 '22 16:10

X.Creates


Lorem's answer mostly worked for me. I got unrecognized keyword setting headers: on the get. I modified the authenticated_header method and put it in support/api_helper.rb so I could reuse it. The modification is to merge the auth token into request.headers.

# spec/support/api_helper.rb
module ApiHelper
  def authenticated_header(request, user)
    token = Knock::AuthToken.new(payload: { sub: user.id }).token
    request.headers.merge!('Authorization': "Bearer #{token}")
  end
end

In each spec file testing the api, I include api_helper.rb. And I call authenticated_header just before the get statement when testing the case of valid authentication...

# spec/controllers/api/v2/search_controller_spec.rb
RSpec.describe API::V2::SearchController, type: :controller do
  include ApiHelper
...
describe '#search_by_id' do
  context 'with an unauthenticated user' do
    it 'returns unauthorized' do
      get :search_by_id, params: { "id" : "123" }
      expect(response).to be_unauthorized
    end
  end

  context 'with an authenticated user' do
    let(:user) { create(:user) }

    it 'renders json listing resource with id' do
      expected_result = { id: 123, title: 'Resource 123' }
      authenticated_header(request, user)
      get :search_by_id, params: { "id" : "123" }
      expect(response).to be_successful
      expect(JSON.parse(response.body)).to eq expected_result
    end
  end

The key lines in this second test are...

authenticated_header(request, user)
get :search_by_id, params: { "id" : "123" }
like image 25
E L Rayle Avatar answered Oct 20 '22 17:10

E L Rayle