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.
Related
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' })
I am considerably new to OAuth authentication scheme. What pose a problem for me at the moment is getting access_token from server in Rails app.
So far I read few articles covering methods related to Resource Owner Password Credentials in OAuth 2.0, but still it got me nowhere.
To name a few Official documentation regarding ROPC / Introduction to OAuth2 / Description of OAuth2 gem from Intridea
Server that I want to connect with allows password grant. It's deployed by 3rd party, so I assume everything is ok with it. On manual page they defined example of authorization as follows:
curl -X POST -d
'grant_type=password&username=USER_EMAIL&password=YOUR_PASSWORD&client_id=CLIENT_ID&client_secret=CLIENT_SECRET'
'https://auth.example.com/oauth2/token'
I posses all data which is mentioned above. BTW, client_id and client_secret are generic values enclosed in documentation. Server uses Doorkeeper gem to implement OAuth2.
To retrieve access_token from server, I simply put advised by Doorkeeper's wiki code into one of my controllers. Testing ROPC for Doorkeeper
My code in Rails API app utilizing OAuth2 gem from Intridea:
def test
client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET,
site: 'https://auth.example.com/oauth2/token')
access_token = client.password.get_token(username, password)
Rails.logger.info access_token.token
end
What I get after visiting localhost/test is Completed 500 Internal Server Error with OAuth2::Error saying that page that I look for doesn't exist.
When trying just use curl from command line with respective data, I recieve:
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."
Please kindly advise what may cause problem in these set-up.
As it seems I overlooked one important thing, we should declare explicitly token_url in relation to site address and not treat site parameter as entire path.
So in order to request access_token my method should look like this:
def test
client = OAuth2::Client.new(client_id,
client_secret,
token_url: "/oauth2/token",
site: "https://www.example.com/" )
access_token = client.password.get_token(username, password)
Rails.logger.info access_token.token
end
Here you can find similar issue to mine.
If someone wants to get access token with password credentials using simple http method, here is example how to approach this thing:
def test
param = {
:client_id => client_id,
:client_secret => client_secret,
:grant_type => 'password',
:username => username,
:password => password
}
uri = URI.parse(service_url)
http = Net::HTTP.new(uri.host, uri.port)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request.body = param.to_query
response = http.request(request)
Rails.logger.info response.body()
end
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.
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
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