I'm trying to get offline access to google contacts using the oauth2 ruby Gem (https://github.com/intridea/oauth2). My current code is:
#!/usr/bin/env ruby
require 'oauth2'
require 'yaml'
require 'pp'
auth = YAML.load_file('.auth.yml')
client_id = auth['google']['clientid']
client_secret = auth['google']['secret']
redirect = 'urn:ietf:wg:oauth:2.0:oob'
code = ARGV[0]
token = nil
client = OAuth2::Client.new(client_id, client_secret, site: 'https://accounts.google.com', token_url: '/o/oauth2/token', authorize_url: '/o/oauth2/auth')
if code
token = client.auth_code.get_token(code, :redirect_uri => redirect)
auth['google']['token'] = token.to_hash
open('.auth.yml', 'w'){|f| f.write(auth.to_yaml)}
elsif auth['google']['token']
token = OAuth2::AccessToken.from_hash(client, auth['google']['token'])
token.refresh! if token.expired?
auth['google']['token'] = token.to_hash
open('.auth.yml', 'w'){|f| f.write(auth.to_yaml)}
else
puts client.auth_code.authorize_url(scope: 'https://www.google.com/m8/feeds', redirect_uri: redirect, access_type: :offline)
end
pp token.expired? if token
I can use this to get an access token, valid for one hour, and I do see a refresh_token in the response I get when I submit the code I get from the confirmation screen at the generated URL, but the confirmation screen only prompts me for access to my contacts, it does not ask whether I want to give offline access, and the token does indeed expire after an hour. What am I doing wrong?
Every Access token expires after one hour. when you don't request a offline, the user has to grant access again in order to get a new access token.
When requesting offline access token, as you mentioned, you will receive a refresh token. You will use this refresh token to request a new access token after or before it expires. This is done so the user wont be bothered every hour to grant access.
Here is the documentation about refresh token: https://developers.google.com/identity/protocols/OAuth2WebServer#refresh
Related
I am accessing gmail api. I am using ruby to get the access token. However, I need to get a refresh token with the access token. I am using signet as suggested here.
https://readysteadycode.com/howto-access-the-gmail-api-with-ruby
The code looks like this
client = Signet::OAuth2::Client.new({
client_id: ENV.fetch('GOOGLE_API_CLIENT_ID'),
client_secret: ENV.fetch('GOOGLE_API_CLIENT_SECRET'),
token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
redirect_uri: url_for(:action => :callback),
code: params[:code]
})
// this is undefined
refre_token = client.refresh_token
response = client.fetch_access_token!
access_token = response['access_token']
I tried adding additional parameters like this as suggested here
additional_parameters: {
"access_type" => "offline", # offline access
"include_granted_scopes" => "true", # incremental auth
}
to the client.new call but did not seem to have any effect.
How do I get the refresh token from Signet::OAuth2?
The refresh token will only appear the first time you are authenticated.
Follow these steps to remove the stored access so you can be authenticated like the first time.
Manage your google account -> Security -> Manage 3rd party access -> your app name -> remove access
Note: From my testing, it does not appear that the additional parameters property is necessary.
Now I took a sample code of Twitter v2 API from this link. This sample code shows how OAuth and twitter v2 API work for positng a tweet. It works fine with my consumer key and consumer secret.
And I want to simplify the code like below. It assumes that the access token and access token secret are already known and it skips the process of user's approval, like providing the URL that provides PIN.
require 'typhoeus'
require 'json'
consumer_key = CONSUMER_KEY
consumer_secret = CONSUMER_SECRET
token = ACCESS_TOKEN
token_secret = ACCESS_TOKEN_SECRET
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, :site => 'https://api.twitter.com')
options = {
:method => :post,
headers: {
"User-Agent": "v2CreateTweetRuby",
"content-type": "application/json"
},
body: JSON.dump("Hello, world!")
}
create_tweet_url = "https://api.twitter.com/2/tweets"
request = Typhoeus::Request.new(create_tweet_url, options)
access_token = OAuth::Token.new(token, token_secret)
oauth_params = {:consumer => consumer, :token => access_token}
oauth_helper = OAuth::Client::Helper.new(request, oauth_params.merge(:request_uri => create_tweet_url))
request.options[:headers].merge!({"Authorization" => oauth_helper.header}) # Signs the request
response = request.run
puts response
Then, I see the below error message.
ruby test_tweet.rb
/usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/request_proxy.rb:18:in `proxy': Typhoeus::Request (OAuth::RequestProxy::UnknownRequestType)
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:12:in `build'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/signature.rb:23:in `sign'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:49:in `signature'
from /usr/local/lib/ruby/gems/3.1.0/gems/oauth-0.5.10/lib/oauth/client/helper.rb:82:in `header'
from test_tweet.rb:28:in `<main>'
When I used irb and tried step by step, this error happens at oauth_helper.header. As this is the first time to use OAuth API, I may be making some easy mistakes. Does anybody find anything wrong in my code?
I confirmed that my access token and access token secret work at https://web.postman.co/.
Thanks.
You need to insert
require 'oauth/request_proxy/typhoeus_request'
and your code may complete task you desire.
Other lines looks good to me!
In oauth/request_proxy.rb, oauth library check class of request object.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy.rb
return request if request.is_a?(OAuth::RequestProxy::Base)
klass = available_proxies[request.class]
# Search for possible superclass matches.
if klass.nil?
request_parent = available_proxies.keys.find { |rc| request.is_a?(rc) }
klass = available_proxies[request_parent]
end
raise UnknownRequestType, request.class.to_s unless klass
By requiring 'oauth/request_proxy/typhoeus_request', Typhoeus::Request inherits OAuth::RequestProxy::Base and raising UnknownRequestType error can be avoided.
https://github.com/oauth-xx/oauth-ruby/blob/master/lib/oauth/request_proxy/typhoeus_request.rb
I'm building a Slack bot, with a backend Sinatra app. The function of the bot is to add and read entries from Google Calendar.
I'm using session and environment variables.
I'm unable to open up the authorization webpage for Oauth and get access to user's Calendar data. Please help!
Here's my code:
#The redirect_url entered in Google Console.
#Google Oauth redirects to this endpoint once user has authorised request.
get '/oauthcallback' do
client = Signet::OAuth2::Client.new({
client_id: ENV['CALENDAR_CLIENT_ID'],
client_secret: ENV['CALENDAR_CLIENT_SECRET'],
authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
redirect_uri: "https://agile-stream-68169.herokuapp.com/oauthcallback",
code: params[:code]
})
response = client.fetch_access_token!
session[:access_token] = response['access_token']
redirect url_for(:action => :calendars)
end
#The Authorization url which checks if credentials are valid.
get '/authorize' do
# NOTE: Assumes the user is already authenticated to the app
user_id = request.session['user_id']
credentials = authorizer.get_credentials(user_id, request)
if credentials.nil?
redirect authorizer.get_authorization_url(login_hint: user_id, request: request)
end
end
#METHOD: Initialising the API by creating a Calendar Service
def intialize_api
$service = Google::Apis::CalendarV3::CalendarService.new
$service.client_options.application_name = ENV['CALENDAR_APPLICATION_NAME']
$service.authorization = auth_calendar
end
def auth_calendar
client = Signet::OAuth2::Client.new({
client_id: ENV['CALENDAR_CLIENT_ID'],
client_secret: ENV['CALENDAR_CLIENT_SECRET'],
authorization_uri: 'https://accounts.google.com/o/oauth2/auth',
scope: Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY,
redirect_uri: "https://agile-stream-68169.herokuapp.com/oauthcallback"
})
redirect client.authorization_uri.to_s
end
I try to use login_with_oauth2 with google_drive, but I can't understand what is authorization code in #google_drive.rb doc
client = OAuth2::Client.new(
"522807807986-gjotv2np4tdqp4do8sq0gds0p2bqugtf.apps.googleusercontent.com",
'fmWlfzejvx_UtS3CKq2Sl-WQ',
:site => "https://accounts.google.com",
:token_url => "/o/oauth2/token",
:authorize_url => "/o/oauth2/auth"
)
auth_url = client.auth_code.authorize_url(
:redirect_uri => "urn:ietf:wg:oauth:2.0:oob
http://localhost"
)
# Redirect the user to auth_url and get authorization code from redirect URL.
authorization_code = ''
auth_token = client.auth_code.get_token(
authorization_code, :redirect_uri => "urn:ietf:wg:oauth:2.0:oob
http://localhost")
session = GoogleDrive.login_with_oauth(auth_token.token, 'http://localhost:8087')
OAuth2 is the two-steps authorization mechanism. AFAIU, you have your code simply copy-pasted from official documentation, but unfortunately that’s not a working code. It’s just an example. Take a look at the comment marked red there: it’s very important:
# Redirect the user to auth_url and get authorization code from redirect URL.
You are to specify the correct pingback URI for Google server to send the response with your authorization_code (now there states localhost, which is problematically for Google to understand your IP to send you feedback.)
The summing up: you provide Google with credentials, it replies with JSON, containing your authorization_code to the address you specified. After this phase is successfully completed, you may proceed requesting auth_token.
I am trying to implement facebook authentication for an app with warden, after the user allows facebook auth and redirects to my app callback with the token I get a 400 while consuming the api. My warden strategy is this:
class Facebook < Warden::Strategies::Base
def client
#client ||= OAuth2::Client.new MyApp::Facebook::AppID, MyApp::Facebook::AppSecret, :site => 'https://graph.facebook.com'
end
def params
#params ||= Rack::Utils.parse_query(request.query_string)
end
def authorize_url
client.web_server.authorize_url :redirect_uri => request.url, :scope => 'email,publish_stream'
end
def authenticate!
throw(:halt, [302, {'Location' => authorize_url}, []]) unless params['code']
facebook = client.web_server.get_access_token params['code'], :redirect_uri => request.url
rescue OAuth2::HTTPError => e
puts e.response.body
end
end
Strategies.add :facebook, Facebook
The result of printing the response body is this:
{"error":{"type":"OAuthException","message":"Error validating client secret."}}
I am pretty shure the app id and app secret are the ones provided by FB.
Thanks.
I've seen that error message many times. Here are the things I would double check:
your domain is the same as what you listed in the facebook callback url
the app id is correct (actually print this out on a page, sometimes y
the app secret is correct
Add redirect_uri while creating the object of facebook that will fix the issue.
Redirect the user to https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL
After user click allow, it'll hit our Redirect Uri
At that point we'll get the code and we need to do a server side HTTP Get to the following Url to exchange the code with our oAuth access token:
https://graph.facebook.com/oauth/access_token?
client_id=YOUR_APP_ID&redirect_uri=YOUR_URL&
client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE
Now at step 3, I kept on getting Http 400 response back.
So after some research, I found out that on that redirect_uri that we submitted on step 3 doesn't do anything but validate the request. Thus, the value need to match with step 2.
I also get the same error and i resolved by doing as below:
double check your client_id, client_secret, redirect_uri.
Add Accept: "application/json" header to thye request
fetch(
`https://graph.facebook.com/v15.0/oauth/access_token?client_id=${process.env.FACEBOOK_APP_ID}&redirect_uri=${process.env.FACEBOOK_REDIRECT_URI}&client_secret=${process.env.FACEBOOK_APP_SECRET}&code=${code}`,
{
method: "GET",
headers: {
Accept: "application/json",
},
}
)