I've been trying to get a basic provider/ client setup running for a couple days now. Yet no matter what I do my client app is getting an invalid_grant error.
I've combed through the repo to better understand what would cause and invalid_grant error and it seems it should only be fired if the access_grant has been revoked.
When my client application fails due to the invalid_grant error devise is properly displaying the "invalid credentials error. When I check the db there is an unused access_grant. If grab the access_grants's token and use a ruby console everything works fine.
Example:
Loading development environment (Rails 4.2.4)
[1] pry(main)> callback = "http://localhost:3001/users/auth/doorkeeper/callback"
=> "http://localhost:3001/users/auth/doorkeeper/callback"
[2] pry(main)> app_id = "867e46cc052aafc5c580d328daa475d7024328df71eedfe3d640e197f651f0ed"
=> "867e46cc052aafc5c580d328daa475d7024328df71eedfe3d640e197f651f0ed"
[3] pry(main)> secret = "857c7e1f887d52064703ef19852032729e321643ab4638d302ab33eb0afe1a57"
=> "857c7e1f887d52064703ef19852032729e321643ab4638d302ab33eb0afe1a57"
[4] pry(main)> client = OAuth2::Client.new(app_id, secret, site: "http://localhost:3000")
=> #<OAuth2::Client:0x007f94d01fb0c8
@id="867e46cc052aafc5c580d328daa475d7024328df71eedfe3d640e197f651f0ed",
@options={:authorize_url=>"/oauth/authorize", :token_url=>"/oauth/token", :token_method=>:post, :connection_opts=>{}, :connection_build=>nil, :max_redirects=>5, :raise_errors=>true},
@secret="857c7e1f887d52064703ef19852032729e321643ab4638d302ab33eb0afe1a57",
@site="http://localhost:3000">
[5] pry(main)> client.auth_code.authorize_url(redirect_uri: callback)
=> "http://localhost:3000/oauth/authorize?client_id=867e46cc052aafc5c580d328daa475d7024328df71eedfe3d640e197f651f0ed&redirect_uri=http%3A%2F%2Flocalhost%3A3001%2Fusers%2Fauth%2Fdoorkeeper%2Fcallback&response_type=code"
The resulting url will fail to authenticate and return {"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."}
So I go to the database and grab the unused access grant token and continue. _NOTE: At this point and time the access grant token revoked_at is still NULL_
[6] pry(main)> access_grant_token = "fe0d66bb2d5445f19337ac15f6973a6042b45055bc5973faa9d9820d95f49464"
=> "fe0d66bb2d5445f19337ac15f6973a6042b45055bc5973faa9d9820d95f49464"
[7] pry(main)> access = client.auth_code.get_token(access_grant_token, redirect_uri: callback)
=> #<OAuth2::AccessToken:0x007f94cf7482e8
@client=
#<OAuth2::Client:0x007f94d01fb0c8
@auth_code=#<OAuth2::Strategy::AuthCode:0x007f94cfba3338 @client=#<OAuth2::Client:0x007f94d01fb0c8 ...>>,
@connection=
#<Faraday::Connection:0x007f94cfba3158
@builder=
#<Faraday::RackBuilder:0x007f94cfba2dc0
@app=
#<Faraday::Request::UrlEncoded:0x007f94d0960228
@app=
#<Faraday::Adapter::NetHttp:0x007f94d09602a0 @app=#<Proc:0x007f94d0960390@/Users/jonathan/.rvm/gems/ruby-2.2.2/gems/faraday-0.9.2/lib/faraday/rack_builder.rb:152 (lambda)>>>,
@handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>,
@default_parallel_manager=nil,
@headers={"User-Agent"=>"Faraday v0.9.2"},
@options=#<struct Faraday::RequestOptions params_encoder=nil, proxy=nil, bind=nil, timeout=nil, open_timeout=nil, boundary=nil, oauth=nil>,
@parallel_manager=nil,
@params={},
@proxy=nil,
@ssl=
#<struct Faraday::SSLOptions
verify=nil,
ca_file=nil,
ca_path=nil,
verify_mode=nil,
cert_store=nil,
client_cert=nil,
client_key=nil,
certificate=nil,
private_key=nil,
verify_depth=nil,
version=nil>,
@url_prefix=#<URI::HTTP http://localhost:3000/>>,
@id="867e46cc052aafc5c580d328daa475d7024328df71eedfe3d640e197f651f0ed",
@options={:authorize_url=>"/oauth/authorize", :token_url=>"/oauth/token", :token_method=>:post, :connection_opts=>{}, :connection_build=>nil, :max_redirects=>5, :raise_errors=>true},
@secret="857c7e1f887d52064703ef19852032729e321643ab4638d302ab33eb0afe1a57",
@site="http://localhost:3000">,
@expires_at=1445794685,
@expires_in=7200,
@options={:mode=>:header, :header_format=>"Bearer %s", :param_name=>"access_token"},
@params={"token_type"=>"bearer", "scope"=>"user", "created_at"=>1445787485},
@refresh_token=nil,
@token="00c560260d550167c871ecf89567d9a07e826e896072ae1a209960e0fe182b93">
[8] pry(main)> access.get("/me").parsed
=> {"id"=>1, "email"=>"[email protected]", "created_at"=>"2015-10-23T04:20:30.871Z", "updated_at"=>"2015-10-25T04:19:20.137Z"}
What am I doing wrong here? Why does my client constantly return and invalid grant_error.
So after vendoring the gem locally and putting adding ruby puts entries everywhere to track the sequence of events, it turns out the authorization is failing because of this rediret_url validation
So I modified the to see exactly what was going on like so:
# my modified method
def validate_redirect_uri
puts "client redirect url is: #{redirect_uri}"
puts "grant redirect uri is: #{grant.redirect_uri}"
grant.redirect_uri == redirect_uri
end
# which outputs
client redirect uri is: http://localhost:3001/users/auth/doorkeeper/callback?code=dcf15a0e87ae6b168b4ae3ec5d840fb4e2acb62146b8b06a9a2005d3ce2a6467&state=cb154ebce1b3f7f42547d867c25ec0650579a3b7be3adc01
grant redirect uri is: redirect_uri: http://localhost:3001/users/auth/doorkeeper/callback
So the urls are different, but of course they are the client redirect_uri won't have the code and state since those are dynamic attributes.. right?
How did this ever work?
My problem seems similar to this issue in the Omniauth-Facebook repo. https://github.com/mkdynamic/omniauth-facebook/issues/220
It looks as if the strategy in the client-app is what defines the callback url. So maybe there is something wrong with mine?
# lib/omniauth/strategies/doorkeeper.rb
module OmniAuth
module Strategies
class Doorkeeper < OmniAuth::Strategies::OAuth2
option :name, :doorkeeper
option :client_options, {
site: 'http://localhost:3000',
authorize_path: 'oauth/authorize'
}
uid do
raw_info['id']
end
info do
{
:email => raw_info['email']
}
end
def raw_info
@raw_info ||= access_token.get('/me').parsed
end
end
end
end
Could this issue somehow be a problem with devise?
So after days of frustration It turns out this was all because of a change in the omniauth-oauth2 repo
This issue reports the problem. The fix is to just roll back to 1.3.1 or override the callback_url method in your strategy.
@jonathansimmons Thanks so much for posting your results here! This was driving me nuts for almost an hour..
@rjayroach :+1:
Really appreciate you posting your debugging here! @jonathansimmons
@jonathansimmons Thanks, really help me! :+1:
@jonathansimmons Thanks!
@jonathansimmons thanks a lot :frog:
Thanks for your notes on this, this was really helpful.
It makes me sad that this still hasn't been fixed in omniauth-oauth2.
Thank you so much. I spent almost two days checking all parts of my doorkeeper provider and client to see what was wrong. Why did all of it suddenly stop working.
Thank you so much. I reverted the gem version and it all started working again. :)
Thanks for notes of this, I solved the same problem @jonathansimmons
Thank you so much for working through this bug here. I got to the omniauth downgrade as a result of it. Thanks.
Still dont working for me =/
even after the downgrade. Any other solution, folks?
@igorrfc I made a local copy of the Doorkeeper gem, version 3.1.0. In Doorkeeper::Server I added the second line below to overwrite the redirect_uri.
def parameters
context.request.parameters['redirect_uri'] = context.request.parameters['redirect_uri'].split("?")[0]
context.request.parameters
end
This made the problem go away because (as @jonathansimmons points out), the redirect_uri sometimes had extra parameters at the end such as this:
?code=dcf15a0e87ae6b168b4ae3ec5d840fb4e2acb62146b8b06a9a2005d3ce2a6467&state=cb154ebce1b3f7f42547d867c25ec0650579a3b7be3adc01
I ended not doing this in production and rolling back to a previous version of the gem as suggested above, because it's not clear to me what those extra parameters are used for.
Most helpful comment
So after days of frustration It turns out this was all because of a change in the omniauth-oauth2 repo
This issue reports the problem. The fix is to just roll back to 1.3.1 or override the
callback_urlmethod in your strategy.