简体   繁体   中英

RSpec requests an access token from Doorkeeper and then always gets invalid_grant error

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM