Auth with todoist API: invalid_grant - ruby-on-rails

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.

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.

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

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.

get an access code from the doorkeeper gem

when we request by click on authorize.........
request send to the
http://localhost:3000/oauth/authorize?client_id=57070f3927deea2d38c50afa042ae0o9u0c539e4d45a79e203cd66d286f9ec8e&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F&response_type=code
the response come
http://localhost:3000/?code=1560b332321dd2obc99ed3411c78614ce0d59c90e9264c87b7f2f179441d6b4e
now i hve to copy the "code" put in console like below code.....
app_id = "57070f3927deea2d38c50afa042ae0o9u0c539e4d45a79e203cd66d286f9ec8e"
secret = "1dbd541132ca2bdeb9fe83b41d24490b2be445c30fd1856e5914f6d343c4a71b"
client = OAuth2::Client.new(app_id, secret, site: "http://localhost:3000/")
client.auth_code.authorize_url(redirect_uri: callback)
access = client.auth_code.get_token('1560b332321dd2obc99ed3411c78614ce0d59c90e9264c87b7f2f179441d6b4e', redirect_uri: callback)
access.token
this how the access_token is generated...
is there a better way to get the access code from the dookeeper
This is standard way described by oauth2.0 specification for autorization code based retrieval of access token. There are other ways like implicit, password and client credentials. Check out the details in RFC and try it with Doorkeeper.

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.

Resources