fetch_access_token! in Google API -- "Missing authorization code." - ruby-on-rails

I'm using the Google API Ruby Client get get access to users' calendars.
I get access with:
client_id: "xxxxx"
client_secret: "xxxxx"
access_type: "offline"
approval_type: ""
scope: "https://www.google.com/calendar/feeds/ https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar"
callback_path: "/app/oauth_response"
provider :google_oauth2, GOOGLE_API['client_id'], GOOGLE_API['client_secret'],
{ access_type: GOOGLE_API['access_type'],
approval_prompt: GOOGLE_API['approval_prompt'],
scope: GOOGLE_API['scope'],
callback_path: GOOGLE_API['callback_path'],
path_prefix: GOOGLE_API['path_prefix']}
When the response comes back it has a refresh token, access token, expired_at, etc. I am then able to make API requests with the access code. But once that access code expires (after an hour), I believe I need to use the refresh token to get a new access token, correct?
Here's the call I'm making:
HTTParty.get('https://www.google.com/calendar/feeds/default/owncalendars/full', :query => {:alt => "jsonc", :access_token => access_token})
Which tells me that my token expired. So I try to get a new one.
But when I try to do that I get this:
#client.authorization.fetch_access_token!
ArgumentError Exception: Missing authorization code.
I see that in my #client object, #code=nil. I'm assuming that's what needs to be set, but I don't get a 'code' property returned from my initial request.
How do I get that code, or if I don't need it, what am I doing wrong?
Thanks!

Most likely the the refresh token isn't set in #client.authorization at the point you're calling fetch_access_token!
Take a look at https://github.com/google/signet/blob/master/lib/signet/oauth_2/client.rb
That error message only appears in the fall through for an unknown/unspecified grant_type. grant_type itself is inferred based on the state of the authorization client.
In your use case, trying to refresh a token, it should be 'refresh_token' and grant_type will return that value if a refresh_token is set. My hunch is if you dump the value of #client.authorization.refresh_token and #client.authorization.grant_type they'll both be nil.
The fix would be to just to make sure you properly restore the refresh_token prior to calling that method.

The Ruby client library takes care of automatically getting a new access token from the refresh token when the former expires, so you don't need to do anything to handle that case.

In the client there is following code (https://github.com/google/signet/blob/621515ddeec1dfb6aef662cdfaca7ab30e90e5a1/lib/signet/oauth_2/client.rb#L935), so you need to remove aredirect_uri, when you do not want to get access token.
if self.redirect_uri
# Grant type was intended to be `authorization_code` because of
# the presence of the redirect URI.
raise ArgumentError, 'Missing authorization code.'
end

Related

Trouble Establishing Ouath2 Connection to Microsoft Dynamics via Rails Oauth2 gem

I am new to authentication using Oauth2, and was hoping someone could provide some guidance on how to use the oauth2 gem to correctly perform authentication so as to get a token with Microsoft Dynamics.
I have been able to authorize and get a token with Postman, but as these types of applications greatly facilitate the overall process, it can be difficult to map these things to code, especially when the concepts are new.
For the access token, I have at my disposal:
The Auth URL, which is of the form "https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/authorize?resource=https://api.businesscentral.dynamics.com
The Access Token URL, which is of the form "https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/oauth2/token?resource=https://api.businesscentral.dynamics.com"
The client_id
The client_secret
I've tried the various examples online, but I either get a nondescript error from oauth2 such as:
OAuth2::Error ():
Or, in other cases, something more particular:
OAuth2::Error ({"code"=>"RequestDataInvalid", "message"=>"Request data is invalid."}:
{"error": {"code": "RequestDataInvalid","message": "Request data is invalid."}}):
Does anyone have any real, working examples on how to successfully obtain a token?
Finaly cracked it.
Had to respecitvely move the resource element out of the auth and the access token urls to:
https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/authorize
https://login.windows.net/<CUSTOMER_IDENTIFIER_HASH>/oauth2/token
At that point, i set the client as:
client = OAuth2::Client.new(
client_id,
client_secret,
site: base_url,
grant_type: "client_credentials",
resource: "https://api.businesscentral.dynamics.com",
authorize_url: auth_url,
token_url: token_url,
)
Above, the base_url is:
https://api.businesscentral.dynamics.com/v2.0/<CUSTOMER_IDENTIFIER_HASH>
Then, i call the client to get an auth_code and had to explicitly pass the resource parameter:
client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback', resource: "https://api.businesscentral.dynamics.com")
I'm not sure if the resource is necessary when getting the token, but finally, I obtain it as follows:
token = client.password.get_token(<AUTHENTICATION_LOGIN>, <AUTHENTICATION_PASS>, resource: "https://api.businesscentral.dynamics.com")
then i can use the token to perform get commands:
token.get('https://api.businesscentral.dynamics.com/v2.0/Sandbox/api/v2.0/companies(<COMPANY_ID_HASH>)/customers', :headers => { 'Accept' => 'application/json' })
I have a feeling I have to do some cleanup on the url for the final get command, but it seems to work this way.

Error in getting access token using client_credentials and authorization_code grant_type

We need to consume API in Android application which requires access token. Tried client_credentials and authorization_code method to get the access token.
Getting the following error for client_credentials:
"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid.
Note: We are passing client_id,client_secret,scope and grant_type in the body of the request.
grant_type:client_credentials
client_id:*
client_secret:*
scope:****
The second method which we tried "authorization_code" is returning 200 OK Response without any authorization token.
grant_type:authorization_code
client_id:*
client_secret:
scope:
redirect_uri:*
state:12345
response_type:code
response_mode:query
Any pointers on this will be greatly appreciated
You should try passing scope too along with grant_type etc.
Try
**'client_secret' : 'xxxxxxxxxxxxxxxxxxxxxxxx',
'client_id':'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'scope':'https://graph.microsoft.com/.default',
'grant_type':"client_credentials"**
Try with these parameters and see if it works.
Although in the documentation somewhere is recommended/options but you have to pass scope in the body along with other parameters.
Thanks

OAuth1 reject_token 401 unauthorized

Get Access Token request of OAuth1.0 only work once for Magento1.9 after being redirected back from Authorization URL. Next time when requesting for Access Token I get reject_token.
What I noticed there is difference in signature of both objects' signature.
Request 1(successful):
OAuth::Consumer.new(consumer_data)
OAuth::RequestToken.from_hash(some_hash)
request_token.get_access_token(oauth_verifier: 'asdfasdagbadbv')
with signature having
oauth_nonce=\"iIHmN7obLeONSitOxFFZQI71v0k4mAsEFLFen0Lw\",
oauth_signature=\"lwj0n1AK3VJLdaXHIWEOFlYp5qc%3D\"
Request 2(unsuccessful):
OAuth::Consumer.new(consumer_data)
OAuth::RequestToken.from_hash(some_hash)
request_token.get_access_token(oauth_verifier: 'asdfasdagbadbv')
with signature having
oauth_nonce=\"KciY4tiiPDu1u029Nbdu1C325svchfESTYV1l8mvw\",
oauth_signature=\"KciY4tiiPt5Du1u029Nbdu1CzCHzvc%3D\"
This may be or may not be the issue but this is the only difference I found so far in both requests.
Please someone help me in updating oauth_nonce and signature or devise some other solution.
The problem is in the second line.
request_token.get_access_token(oauth_verifier: 'asdfasdfa')
According to Auth documentation request token should be used one time. Request token expires once we use them. You are using expired request token in the second call which causes reject_token 401 unauthorized.
Solution
Actually, request tokens are used to generate Access Token. Access Tokens can be used multiple times. So what you need is to store Access Token somewhere, generated in first request_token.get_access_token(oauth_verifier: 'asdfasdfa') line. Then you can use saved access token in the reset of your API calls. The syntax of using access token is the following:
#consumer = OAuth::Consumer.new(...)
#token = OAuth::Token.new('ACCESS_TOKEN', 'ACCESS_TOKEN_SECRET') // saved access token and secret here
#consumer.request(:post, '/people', #token, {}, #person.to_xml, { 'Content-Type' => 'application/xml' })

Rails 4 - Google One Time Code Flow with omniauth-google-oauth2, keep getting "Invalid code"

I have a Rails app in which I want users to be able to sign in / up with Google. I'm using the following gem:
#gemfile
gem 'omniauth-google-oauth2'
I've almost got it to work (I actually received the access token once, not sure why) - but before getting the access_token I constantly get the following error:
"error"=>"invalid_grant", "error_description"=>"Invalid code."
I have checked so that the code is unique for each request and that it isn't nil. The relevant parts of the method where I try to get the access_token after I've received the one time authorisation code looks like this:
def google_authentication
respond_to do |format|
# authorisation code
code = params[:code]
unless code.blank?
client_id = ENV["GOOGLE_CLIENT_ID"]
client_secret = ENV["GOOGLE_CLIENT_SECRET"]
redirect_uri = 'postmessage'
grant_type = 'authorization_code'
load = {client_id: client_id, client_secret: client_secret, redirect_uri: redirect_uri, grant_type: grant_type, code: code}
payload = load.to_json
url = "https://www.googleapis.com/oauth2/v3/token"
response = HTTParty.post(url, :query => load)
json = JSON.parse(response.body)
unless json.nil?
unless json[:error].present?
# TODO: Handle data
format.json { render :json => {:message => "Success"} }
else
# ERROR "Invalid code" always happen
end
end
end
end
end
In Google's developer console I have the following credentials:
Client ID [CLient ID]
Email address [Email]
Client secret [Secret]
Redirect URIs http://127.0.0.1:3000/
JavaScript origins http://127.0.0.1:3000
Would be thankful for any ideas or tips.
Update for completion
This is how I set up omniauth-google-oauth2:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"],
{
:scope => "email, profile",
:prompt => "select_account",
:provider_ignores_state => true
}
end
Update 2
As mentioned above I once managed to get the access token once, I managed to reproduce it again. I did it by clicking my sign in button three times.The first time I got:
"error"=>"invalid_grant", "error_description"=>"Invalid code."
The second click resulted in:
"error"=>"invalid_grant", "error_description"=>"Code was already redeemed."
And the third time I successfully got the access_token. I find it really strange that I sometimes get the access_token, but most of the time get:
"error"=>"invalid_grant", "error_description"=>"Invalid code."
And the success / error "rate" isn't 100% consistent. Sometimes it takes more than three clicks. I find it quite strange that it sometimes works and that I sometimes get different error responses without changing anything in my code.
Could it be related to time / expiration date of the code?
Update 3
For additional completion. This is how my Javascript (or CoffeeScript) looks like when the user clicks the Google Sign in button:
$(document).ready ->
$.ajax
url: 'https://apis.google.com/js/client:plus.js?onload=gpAsyncInit'
dataType: 'script'
cache: true
window.gpAsyncInit = ->
$('.googleplus-login').click (e) ->
e.preventDefault()
gapi.auth.authorize {
immediate: false
response_type: 'code'
cookie_policy: 'single_host_origin'
client_id: '[id]'
scope: 'email profile'
}, (response) ->
if response and !response.error
jQuery.ajax
type: 'POST'
url: '/auth/google_oauth2/callback'
dataType: 'json'
data: response
success: (json) ->
# response from server
console.log "JSON: " + json
return
else
# google authentication failed
So ...as it is wrote:
Authorized redirect
URIs One URI per line. Needs to have a protocol,
no URL fragments, and no relative paths. Can't be a public IP Address.
Your setting:
Redirect URIs http://127.0.0.1:3000/
JavaScript origins http://127.0.0.1:3000/
...is wrong.
It should be:
Redirect URIs: http://my.true.domain/users/auth/google/callback
and
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"],
{
:scope => "email, profile",
:name => "google",
...
I hope it help!
I cant help with the ruby part but I may be able to help you figure out what's wrong.
There are in fact 3 codes returned by Google's authentication server. Authorization code, Access token, and refresh token.
Authorization code can only be used once to get the first refresh token.
Access Token used to access data on the apis, expires after an hour.
Refresh Token used to get a new access token when it expires. good until the user removes access.
Authorization code
This is the code that gets returned when the user hits accept to your application.
Example:
Change the client id, secret, and scope in this URI to the ones you are using. then paste it into a browser location bar.
https://accounts.google.com/o/oauth2/auth?client_id={clientid}.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope={scope}&response_type=code
It will prompt you for authentication. If you click except you get another window with a long code in it looking something like this.
That is the Authentication code, its only purpose in life is for you to use it to get an access token and a refresh token. It can only be used once, and its probably short lived though I have never tested how long they are good for.
In the uri above Note: the response type code.
Exchange:
Once you have that authentication code you need to exchange it for an access token and a refresh token. this is a HTTP POST so cant be placed in a browser window.
https://accounts.google.com/o/oauth2/token
code=4/X9lG6uWd8-MMJPElWggHZRzyFKtp.QubAT_P-GEwePvB8fYmgkJzntDnaiAI&client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code
Note: grant_type=authorization_code this tells the server you are sending it an authorization code.
response
{
"access_token" : "ya29.1.AADtN_VSBMC2Ga2lhxsTKjVQ_ROco8VbD6h01aj4PcKHLm6qvHbNtn-_BIzXMw",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/J-3zPA8XR1o_cXebV9sDKn_f5MTqaFhKFxH-3PUPiJ4"
}
You now have an access token that can be used to access the Google APIs, it is short live lasts only 3600 seconds or 1 hour. After that you must use the refresh token to get access again.
Use refreshtoken
https://accounts.google.com/o/oauth2/token
client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
response
{
"access_token" : "ya29.1.AADtN_XK16As2ZHlScqOxGtntIlevNcasMSPwGiE3pe5ANZfrmJTcsI3ZtAjv4sDrPDRnQ",
"token_type" : "Bearer",
"expires_in" : 3600
}
Now that you understand all of that
"error"=>"invalid_grant", "error_description"=>"Code was already redeemed."
means that you are sending the authorization code again you can only use it once you should be sending the refresh token again. There is something up with your authentication flow. Again sorry I cant help with the ruby part.
code ripped from Google 3 legged oauth2 flow

How do I refresh my google_oauth2 access token using my refresh token?

I have a RoR app where I am authenticating against Google using omniauth and google_oauth2 where I am requesting offline access.
How do I use my refresh token to request a current access token? Also, how can I refresh my access token when it no longer works? I don't want to have any user interface in this situation, assuming of course that the authorization hasn't been taken away.
For an example using the Ruby HTTParty gem:
Where #auth is an ActiveRecord record that stores the auth keys for the specific user you are trying to refresh tokens for.
# Refresh auth token from google_oauth2 and then requeue the job.
options = {
body: {
client_id: <YOUR GOOGLE API CLIENT ID HERE>,
client_secret: <YOUR GOOGLE API SECRET KEY HERE>,
refresh_token: #auth.refresh_token,
grant_type: 'refresh_token'
},
headers: {
'Content-Type' => 'application/x-www-form-urlencoded'
}
}
#response = HTTParty.post('https://accounts.google.com/o/oauth2/token', options)
if #response.code == 200
#auth.token = #response.parsed_response['access_token']
#auth.expires_in = DateTime.now + #response.parsed_response['expires_in'].seconds
#auth.save
else
Rails.logger.error("Unable to refresh google_oauth2 authentication token.")
Rails.logger.error("Refresh token response body: #{#response.body}")
end
I don't see anything in google_oauth2 that handles fetching a new access_token with a refresh token, so it looks like you'll need to make the exchange directly.
Google's official OAuth 2.0 documentation explains how to do this at a low level. Within your server-side code, use your favorite HTTP client to construct a request that looks like this:
POST /o/oauth2/token HTTP/1.1
Host: accounts.google.com
Content-Type: application/x-www-form-urlencoded
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET&
refresh_token=REFRESH_TOKEN&
grant_type=refresh_token
where CLIENT_ID and CLIENT_SECRET are the same ones you used for the original authentication and REFRESH_TOKEN is the refresh token from the original authentication flow. If the exchange is successful, you'll receive a fresh access token in a response that looks something like this:
{
"access_token":"1/fFBGRNJru1FQd44AzqT3Zg",
"expires_in":3920,
"token_type":"Bearer",
}
You can follow this process to grab a new access token whenever you need one. You can either use the expires_in value to estimate when you will need a new one, or attempt a refresh whenever your API request responds with a 401 HTTP status.

Resources