1

When I testing the procedure of requesting an access token, which is part of authorize flow, from Doorkeeper gem at localhost side through RSpec with Ruby on Rails, Devise, Grape and Wine_bouncer, RSpec always receives a 401 response from Doorkeeper, whose error description says Invalid-grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.

I want to know how to solve this problem. Please help me, thank you.

The followings are my test environments and code:

  • Ruby 2.3.0
  • Rails 4.2.5.1
  • Doorkeeper 3.1.0
  • Devise 3.5.5
  • RSpec-core 3.4.2, RSpec-support 3.4.1, RSpec-mocks 3.4.1, RSpec-rails 3.4.1, RSpec-expectations 3.4.0
  • Wine_bouncer 0.5.1

Doorkeeper configuration at config/initializers/doorkeeper.rb

Doorkeeper.configure do
    orm :active_record

    resource_owner_authenticator do
        session[:user_return_to] = request.fullpath
        current_user || redirect_to(new_user_session_url)
    end

    authorization_code_expires_in 20.minutes
    access_token_expires_in 30.days

    enable_application_owner :confirmation => false
    default_scopes :public, :description => "Access public data."
    optional_scopes :write, :description => "Update your data."
    optional_scopes :admin, :description => "Do admin things."

    access_token_methods :from_bearer_authorization, :from_access_token_param, :from_bearer_param

    grant_flows %w(authorization_code client_credentials implicit)

    skip_authorization do
        true
    end
end


spec/requests/api/v1/specifiedvegetables_spec.rb

describe SpecifiedVegetables do
    describe 'OAuth client requests the grant' do
        context 'When a REST client sends a request for getting the grant' do

            before(:all) do
                post "http://localhost:3000/users/sign_in?user[email]=test%40test123%2Ecom&user[password]=12345678" # Log in the devise

                @app = Doorkeeper::Application.new :name => "rspectest-107", :redirect_uri => "https://localhost:3000/api/v1/specified_vegetables/", :scopes => "public"
                @app.owner = User.last
                @app.save! # Create OAuth client into database.

                @authorize_code = String.new # Use later for getting authorize grant code
            end

            it 'should getting response code 302 for requesting authorization code.' do
                query_url = "http://localhost:3000/oauth/authorize"
                parameters = { "response_type" => "code", "client_id" => @app.owner.oauth_applications.last.uid, "redirect_uri" => "https://localhost:3000/api/v1/specified_vegetables/", "scope" => "public"}
                headers = {'Content-Type' => 'application/x-www-form-urlencoded'}

                get query_url, parameters, headers # Send request for getting authorize grant code
                expect(response.status).to eq(302)

                authorize_code_param = Rack::Utils.parse_query(URI.parse(response.location).query)
                @authorize_code << authorize_code_param['code'] # Get the authorize grant code
            end

            it 'should get response code 302 for requesting access token.'  do
                query_url = "http://localhost:3000/oauth/token"
                parameters = {"grant_type" => "authorization_code", "code" => @authorize_code, "client_id" => @app.owner.oauth_applications.last.uid, "redirect_uri" => "https://localhost:3000/api/v1/specified_vegetables/"}
                headers = {'Content-Type' => 'application/x-www-form-urlencoded', "Authorization" => "Basic " + Base64.urlsafe_encode64(@app.owner.oauth_applications.last.uid + ":" + @app.owner.oauth_applications.last.secret, :padding => false)}

                post query_url, parameters, headers # Send request for getting access token

                expect(response).to eq(200) # **Receive the Error response**
            end

            after(:all) do
                @app.destroy # After running all test cases, destroy this OAuth client application.
            end
        end
    end
end


Error response after running RSpec command at root directory of rails app.

  • rspec spec/requests/api/v1/specifiedvegetables_spec.rb

expected: 200
got: #, @stream=#, @buf=["{\"error\":\"invalid_grant\",\"error_description\":\"The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.\"}"], @closed=false>, @header={"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "Cache-Control"=>"no-store", "Pragma"=>"no-cache", "Content-Type"=>"application/json; charset=utf-8", "WWW-Authenticate"=>"Bearer realm=\"Doorkeeper\", error=\"invalid_grant\", error_description=\"The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.\"", "X-Request-Id"=>"97099b43-3456-4396-b9d9-cf744ec38ea6", "X-Runtime"=>"0.006156", "Content-Length"=>"213"}, @status=401, @sending_file=false, @blank=false, @cv=#, @cond=#>, @committed=false, @sending=false, @sent=false, @content_type=#, @charset="utf-8", @cache_control={:extras=>["no-store"]}, @etag=nil>

20160306 PM10:39 UTC+8 update:

If I try to get access token through Postman software(get it from Chrome store), I can get it as I expect. But trying to get it through rspec, I can't acquire it.
I have used the modified ruby-type code in RSpec, which are from Postman generate function, I still can't get access token successfully. I think this difference is weird.


20160307 PM03:10 Update:

The related file has been put at github.com . I've gotten an access token through Postman software but I still don't understand why I can acquire access token through RSpec although have been tried many other codes to get. It's still unsolved by RSpec.

Howardsun
  • 116
  • 3
  • 12
  • Even I substitute Base64.strict_encode64 to Base64.urlsafe_encode64 predicate, it's still same error description. – Howardsun Mar 07 '16 at 06:55

2 Answers2

1

I've never used Doorkeeper gem, I use Devise Token Auth. I checked the documentation though and found this. You can stub the :doorkeeper_token method to test protected methods.

let(:token) { double :acceptable? => true }

before do
  controller.stub(:doorkeeper_token) { token }
  # allow(controller).to receive(:doorkeeper_token) {token} # => RSpec 3
end

https://github.com/doorkeeper-gem/doorkeeper/wiki/Testing-protected-controllers

SacWebDeveloper
  • 2,563
  • 2
  • 18
  • 15
  • Thanks for answering but that isn't what I want. What I want is how to test getting the access token for my APIs, which had been built by Grape not in controllers of Rails, without skipping the procedure of authorize flow. – Howardsun Mar 06 '16 at 03:16
  • Something to note, shouldn't your sign in be ```post "http://someaddress", params, headers``` – SacWebDeveloper Mar 06 '16 at 03:44
  • Why? Without sign in, I couldn't get authorize code for OAuth client. By the way, this post predicate is the one for sending request that acquiring access token according to RFC 6750. – Howardsun Mar 06 '16 at 04:52
  • OK, after you modifying your comment, I think I understand what you mean. I will try it later. – Howardsun Mar 07 '16 at 00:16
  • Yeah, I was using the mobile app at the time. By the way, I believe Doorkeeper's spec tests have the token authentication covered. Instead, you may want to just test that your controllers behave as you expect them to returning certain codes and CRUDing the data properly. Stubbing the auth would help you get to the core of the actions. https://github.com/doorkeeper-gem/doorkeeper/blob/master/spec/controllers/protected_resources_controller_spec.rb – SacWebDeveloper Mar 07 '16 at 00:38
  • I've tried to change **post statement for signing in** and it's useless for login, acquiring authorize grant and access token actually. So I change it back. – Howardsun Mar 07 '16 at 06:23
  • I still appreciate your help for providing another direction to this question even though it's unsolved. If I find one solution in the future, I will post it at this thread. – Howardsun Mar 08 '16 at 11:05
1

Sorry, I think I have misunderstood about it-statement usage of RSpec test cases after trying many things.

Right meaning of it-statement in RSpec: Every it-statement is independent between each other even if they're under same context- or describe-statement.

So if I combine two it-statements of requesting authorization code and requesting access token together, I will be at halfway towards passing test with RSpec.

The other thing I need to fix is to use right encoding of Authorization at HTTP header for acquiring access token. Change from Base64.urlsafe_encode64 to Base64.strict_encode64, please.

Following are the right code of spec/requests/api/v1/specifiedvegetables_spec.rb:

require 'rails_helper'

describe SpecifiedVegetables do

    describe 'OAuth client requests the grant' do

        context 'When a REST client sends a request for getting the grant' do
            before(:all) do
                post "http://localhost:3000/users/sign_in?user[email]=test%40test123%2Ecom&user[password]=12345678"
                expect(response.status).to eq(302)
                @app = Doorkeeper::Application.new :name => 'rspectest-107', :redirect_uri => 'https://localhost:3000/api/v1/specified_vegetables/', :scopes => 'public'
                @app.owner = User.last
                @app.save!

                @authorize_code = String.new
            end


            it 'should getting response code 302 for requesting authorization code and access token' do

                query_url = "http://localhost:3000/oauth/authorize"
                parameters = { "response_type" => "code", "client_id" => @app.owner.oauth_applications.last.uid, "redirect_uri" => "https://localhost:3000/api/v1/specified_vegetables/", "scope" => "public"}
                headers = {"content-type" => "application/x-www-form-urlencoded"}

                get query_url, parameters, headers
                expect(response.status).to eq(302)

                authorize_code_param = Rack::Utils.parse_query(URI.parse(response.location).query)
                @authorize_code << authorize_code_param['code']
                # above are acquiring authorize code


                # The following are acquiring access token
                query_url = "http://localhost:3000/oauth/token"
                parameters = { "grant_type" => "authorization_code", "code" => @authorize_code , "client_id" => @app.owner.oauth_applications.last.uid, "redirect_uri" => "https://localhost:3000/api/v1/specified_vegetables/"} 
                headers = {"content-type" => "application/x-www-form-urlencoded", "authorization" => "Basic " + Base64.strict_encode64(@app.owner.oauth_applications.last.uid + ":" + @app.owner.oauth_applications.last.secret), "cache-control" => "no-cache"}

                post query_url, parameters, headers
                expect(response.status).to eq(200) # Here, we get status 200 because the response has access token.

            end

            after(:all) do
                @app.destroy # Destroy the oauth client, but doesn't purge related access token for this rspec request.
            end
        end
    end
end

Howardsun
  • 116
  • 3
  • 12