OAuth1 reject_token 401 unauthorized - ruby-on-rails

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' })

Related

Authentication session is not defined

I try to use Google Photos API to upload my images, base on the steps of the following link.
https://developers.google.com/photos/library/guides/upload-media
After following the Using OAuth 2.0 for Web Server Applications, I just get the Oauth2.0_token response(a JSON format with access_token, refresh_token...). However, after I put this token string with "Bearer " into request headers, the response is error 401, the error message is "code 16 Authentication session is not defined".
I cannot find any information to deal with it, thank for any help.
You probably have incorrect permissions. Make sure you request the token with the appropriate scope. For write-only access you need 'https://www.googleapis.com/auth/photoslibrary.appendonly'
src: https://developers.google.com/photos/library/guides/authentication-authorization#what-scopes
One reason this might be happening is that you initially authorized your user for read-only access. If you went through the authorization flow with a .readonly scope, your bearer token reflects that authorization (and the token is retained in your credentials file). If you change your scope but don't get a new auth token you will get this error when trying to upload. Simply redo the authorization flow with the new scope defined:
SCOPES = 'https://www.googleapis.com/auth/photoslibrary'
store = file.Storage('path_to_store')
if not creds or creds.invalid:
flow = client.flow_from_clientsecrets('google_credentials.json', SCOPES)
creds = tools.run_flow(flow, store)
and your store will be populated with a new token that can be used for uploading.
You say you "just get the Oauth2.0_token response(a JSON format with access_token, refresh_token...)" and "put this token string with "Bearer " into request headers".
Unfortunately documentation on this isn't super clear in a lot of places. What you are supposed to provide after "Bearer" is the "access_token" field only, not the entire JSON string with all the token fields in it. For reference, this is a single string of random looking characters which probably starts with "ya29." and is pretty long - in my case it's 170 characters.

Auth with todoist API: invalid_grant

I'm adding the ability to post todos to my Todist list via a simple app. At the moment I am getting the response "error"=>"invalid_grant" when exchanging my code for an access_token.
I'm unsure exactly what 'invalid_grant' is referring too in this context. Other answers I find seem to be regarding various Google APIs. The Todoist API documentation makes no mention of it.
The post request for token exchange is:
uri = URI('https://todoist.com/oauth/access_token')
result = Net::HTTP.post_form(uri, client_id: ENV['TODOIST_CLIENT_ID'], client_secret: ENV['TODOIST_CLIENT_SECRET'], code: params[:code])
json_body = JSON.parse(result.body) # <- prints error
Any help understanding and solving this is much appreciated.
Update
After reading Takahiko Kawasaki's answer, I have updated the request to the following, but have the same error message.
uri = URI('https://todoist.com/oauth/access_token')
data = {
:client_id => ENV['TODOIST_CLIENT_ID'],
:client_secret => ENV['TODOIST_CLIENT_SECRET'],
:code => params[:code],
:grant_type => 'authorization_code',
}
result = Net::HTTP.post_form(uri, data)
json_body = JSON.parse(result.body)
Add the following.
grant_type: 'authorization_code'
See RFC 6749, 4.1.3. Access Token Request for details.
Additional comment for the revised question.
It seems that the OAuth implementation by Todoist is not mature. I took a look at their API document and soon found some violations against RFC 6749.
For example, (1) scopes must be delimited by spaces but their document says commas should be used. (2) Their token endpoint does not require the grant_type request parameter, which is required by the specification. (3) The value of the error parameter in the response from a token endpoint should be invalid_grant when the presented authorization code is wrong, but their API document says the value will be bad_authorization_code, which is not an official value.
In addition, this is not a violation, but the specification of their API to revoke access tokens implies that they don't know the existence of the official specification for access token revocation, RFC 7009.
For public clients (RFC 6749, 2.1. Client Types), e.g. smartphone applications, the client_secret request parameter of a token endpoint should be optional, but their API document says it is required.
Because their OAuth implementation does not comply with the specification, it would be better for you to ask Todoist directly.
The latest version of the Todoist API (v8) does not require the grant_type parameter so this is not currently the issue.
Two possible reasons for receiving the invalid_grant error are:
The code was not used within a certain length of time and has expired
The code has already been used to generate an access token and so is no longer valid
In both cases, generating a new code before making the POST request should sort the problem.

Authenticating to an API with a token

I'm working with the Zendesk API, an HTTPS-only, JSON API and authentication is required to update a resource, and the API supplies an API token to use when authenticating as different users. When updating a resource, I issue a PUT request over SSL, assign the appropriate JSON content to the request body, and specify the Content-Type request header as application/json.
Next, the API instructs its users to authenticate as the end-user by either using the user's email and password (which I can't do for several reasons) or to use the user's email address along with the API token. The following is my attempt to authorize to the API with the Authorization header:
#id = params[:id]
#comment_body = params[:comment]
uri = URI.parse "https://{subdomain}.zendesk.com/api/v2/requests/#{#id}.json"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
req = Net::HTTP::Put.new(uri.request_uri)
req.body = '{"request": {"comment":{"value":' + "\"#{#comment_body}\"" + '}}}'
req['Content-Type'] = 'application/json'
#The following two lines do not work!
credentials = Base64::encode64("{user_email}/token:{api_token}")
request.headers['Authorization'] = "Basic #{credentials}"
response = http.request(req)
The API specifies that the format for authentication using the API token is {user_email}/token:{api_token}. I encoded that format with Base64::encode64 and passed it to the Authorization Header preceded with Basic, but the response is a 401 Unauthorized. However, replacing those two lines with req.basic_auth {user_email}, {user_password} works fine.
So my question is, how can I authenticate as a different user using the email and the given API token as authentication instead of supplying the user's email and password to req.basic_auth?
The googling I've done on the topic has revealed very little; apparently it's a lot more common to use the normal {username}:{password} format when dealing with the Authorization header than an API token.
Thanks in advance!!
Update: Weirdly, trying to authenticate as the end-user with req['Authorization'] = "Basic #{credentials}" does not return a 401 Unauthorized Error or a WWW-Authenticate header while trying to authorize as request.headers['Authorize'] = "Basic #{credentials}" does.
Finally figured it out after much head-banging and nearly throwing my laptop out the window. Suddenly, the answer seems incredibly obvious.
When using Net::HTTP, its basic_auth method can also accept tokens depending on the API, and the Zendesk API specifies that the format for using the API token is {email}/token:{token}. Basic authentication uses the format {username}:{password}, where the two fields are separated by a colon, meaning in Zendesk's case I can place {email}/token as the first argument and {token} as the second argument (instead of the username as the first argument and the password as the second argument), so the following code is correct:
req.basic_auth "{email}/token", "{api_token}"
I hope anyone who found this useful could leave a comment. Nice to know I spared someone from this frustration.

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.

fetch_access_token! in Google API -- "Missing authorization code."

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

Resources