Trouble authenticating with Google Content API for Shopping - ruby-on-rails

I'm trying to use OAuth2 for Server to Server Applications in conjunction with Google's Content API for Shopping using the google-api-client gem and Ruby on Rails 3.2.5. Also, I have already set up my merchant account as prescribed in the Content API documentation.
This was the best way I found to be able to:
create/update products in the background
have created products fall under my company's Google Products 'umbrella'
not require every user to authenticate/authorize when their token expires
Using lines 1 - 23 from this sample as a starting point, I've begun to write the following module for use in background jobs:
require 'httparty'
require 'google/api_client'
module GoogleProducts
GOOGLE_CONFIG = YAML.load_file(File.join(Rails.root, "config", "google.yml"))[Rails.env]
CLIENT_ID = "XXXXXXXXXXXX#developer.gserviceaccount.com"
MERCHANT_ID = "XXXXXXX"
SCOPE = "https://www.googleapis.com/auth/structuredcontent"
KEY_FILE_PATH = File.join(Rails.root, "config", "my-privatekey.p12")
KEY_FILE_PASS = "XXXXXXXXXX"
def self.add_item(item_id)
self.fetch_token
xml = self.gen_item_xml(item_id)
headers = {"Content-type" => "application/atom+xml", "Content-Length" => xml.length.to_s}
url = "https://content.googleapis.com/content/v1/#{MERCHANT_ID}/items/products/generic?access_token=#{$gp_token}"
response = HTTParty.post(url, :body => xml, :headers => headers).parsed_response
end
def self.gen_item_xml(item_id)
#building product xml
end
private
def self.fetch_token
api_client = Google::APIClient.new(:authorization => :oauth2)
key = Google::APIClient::PKCS12.load_key(KEY_FILE_PATH, KEY_FILE_PASS)
asserter = Google::APIClient::JWTAsserter.new(CLIENT_ID, SCOPE, key)
begin
api_client.authorization = asserter.authorize
#todo - store in something other than a global
$gp_token = api_client.authorization.access_token
rescue Signet::AuthorizationError => e
puts e.message
ensure
return $gp_token
end
end
end
Everything seemingly works fine - the authentication, the handling of the auth token - until I attempt to actually add an item, which I get the following when I do:
<errors xmlns='http://schemas.google.com/g/2005'>
<error>
<domain>GData</domain>
<code>ServiceForbiddenException</code>
<internalReason>Could not find authenticated customer</internalReason>
</error>
</errors>
Any ideas?

After much anguish and mental toil, I've finally solved my issue!
Since I am using OAuth 2 Server to Server authentication the suggestion hjblok gave didn't apply (thanks for giving it a shot, though!).
I simply added the email address that was associated with my Service Account key from the Google API Console (e.g. XXXXXXXXXXXX#developer.gserviceaccount.com) to my Google Merchant account (Settings > Users on the merchant admin page), and it worked.
If there's any clarification needed, please feel free to comment!

The Google Content API documentation says you need to set it up in the Settings page of the Google Merchant Center:
https://developers.google.com/shopping-content/getting-started/usingapi-products

EDIT rewrote the answer after diving into the Google's API documentation
Did you already try to use Google's OAuth 2.0 playground? I was able to successfully access https://content.googleapis.com/content/v1/#{MERCHANT_ID}/items/products/generic.
In "Step 1" I've chosen the "Content API for Shopping" and then authorized the API with my account.
Then in "Step 2" I've "exchanged authorization code for tokens", which results in a "refresh token" and an "access token".
Then in "Step 3" I've invoked a GET request to https://content.googleapis.com/content/v1/1234567/items/products/generic. Because 1234567 is not a valid MERCHANT_ID it returns an Error. But the Error Messages contains a MERCHANT_ID which actually belongs to your account.
I repeated "Step 3" but now with the correct MERCHANT_ID. Which returns a HTTP/1.1 200 OK with the requested items in the body.
Furthermore I'm not sure, but doesn't Google API expect an Authorization header to be present with the access_token ($gp_token)? Within the OAuth 2.0 playground this Authorization header is used to sent the access_token.
I also found the Structured Content API demo page (https://google-content-api-tools.appspot.com/demo/demo.html), which is more specific to the Content API for Shopping.

Related

Google OAuth 2.0 failing with Error 400: invalid_request for some client_id, but works well for others in the same project

We have some apps (or maybe we should call them a handful of scripts) that use Google APIs to facilitate some administrative tasks. Recently, after making another client_id in the same project, I started getting an error message similar to the one described in localhost redirect_uri does not work for Google Oauth2 (results in 400: invalid_request error). I.e.,
Error 400: invalid_request
You can't sign in to this app because it doesn't comply with Google's
OAuth 2.0 policy for keeping apps secure.
You can let the app developer know that this app doesn't comply with
one or more Google validation rules.
Request details:
The content in this section has been provided by the app developer.
This content has not been reviewed or verified by Google.
If you’re the app developer, make sure that these request details
comply with Google policies.
redirect_uri: urn:ietf:wg:oauth:2.0:oob
How do I get through this error? It is important to note that:
The OAuth consent screen for this project is marked as "Internal". Therefore any mentions of Google review of the project, or publishing status are irrelevant
I do have "Trust internal, domain-owned apps" enabled for the domain
Another client id in the same project works and there are no obvious differences between the client IDs - they are both "Desktop" type which only gives me a Client ID and Client secret that are different
This is a command line script, so I use the "copy/paste" verification method as documented here hence the urn:ietf:wg:oauth:2.0:oob redirect URI (copy/paste is the only friendly way to run this on a headless machine which has no browser).
I was able to reproduce the same problem in a dev domain. I have three client ids. The oldest one is from January 2021, another one from December 2021, and one I created today - March 2022. Of those, only the December 2021 works and lets me choose which account to authenticate with before it either accepts it or rejects it with "Error 403: org_internal" (this is expected). The other two give me an "Error 400: invalid_request" and do not even let me choose the "internal" account. Here are the URLs generated by my app (I use the ruby google client APIs) and the only difference between them is the client_id - January 2021, December 2021, March 2022.
Here is the part of the code around the authorization flow, and the URLs for the different client IDs are what was produced on the $stderr.puts url line. It is pretty much the same thing as documented in the official example here (version as of this writing).
OOB_URI = 'urn:ietf:wg:oauth:2.0:oob'
def user_credentials_for(scope, user_id = 'default')
token_store = Google::Auth::Stores::FileTokenStore.new(:file => token_store_path)
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store)
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url(base_url: OOB_URI)
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts url
code = $stdin.readline.chomp
$stderr.puts "-----------------------------------------------"
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code, base_url: OOB_URI)
end
credentials
end
Please see https://stackoverflow.com/a/71491500/1213346 for a "proper" solution. This answer is just an ugly workaround that the community seems to like.
...
Here is a cringy workaround for this situation:
Replace urn:ietf:wg:oauth:2.0:oob with http://localhost:1/ in the code posted in the question. This makes the flow go through, my browser gets redirected and fails and I get an error messages like:
This site can’t be reached
The webpage at http://localhost:1/oauth2callback?
code=4/a3MU9MlhWxit8P7N8QsGtT0ye8GJygOeCa3MU9MlhWxit8P7N8QsGtT0y
e8GJygOeC&scope=email%20profile%20https... might be temporarily
down or it may have moved permanently to a new web address.
ERR_UNSAFE_PORT
Now copy the code code value from the failing URL, paste it into the app, and voila... same as before :)
P.S. Here is the updated "working" version:
def user_credentials_for(scope, user_id = 'default')
token_store = Google::Auth::Stores::FileTokenStore.new(:file => token_store_path)
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store, "http://localhost:1/")
credentials = authorizer.get_credentials(user_id)
if credentials.nil?
url = authorizer.get_authorization_url
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts url
$stderr.puts
$stderr.puts "At the end the browser will fail to connect to http://localhost:1/?code=SOMECODE&scope=..."
$stderr.puts "Copy the value of SOMECODE from the address and paste it below"
code = $stdin.readline.chomp
$stderr.puts "-----------------------------------------------"
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id, code: code)
end
credentials
end ```
I sent off an email to someone on the Google OAuth team. This is the gist of their response.
As I feared your issue is related to Making Google OAuth interactions safer by using more secure OAuth flows
The current recommendation from google is to move to use localhost/loopback redirects as recommended here: instructions-oob or use the OAuth for devices flow if you are using non-sensitive scopes and need a headless solution.
A solution for python.
As google_auth_oauthlib shows, InstalledAppFlow.run_console has been deprecated after Feb 28, 2022. And if you are using google-ads-python, you can just replace flow.run_console() by flow.run_local_server().
Let me post the "proper" solution as a separate answer, which is to actually follow the recommended procedure by implementing an HTTP listener in the ruby app. If this is running on an offline machine the listener will never get the code, but you can still paste the code from the failing URL.
require 'colorize'
require 'sinatra/base'
# A simplistic local server to receive authorization tokens from the browser
def run_local_server(authorizer, port, user_id)
require 'thin'
Thin::Logging.silent = true
Thread.new {
Thread.current[:server] = Sinatra.new do
enable :quiet
disable :logging
set :port, port
set :server, %w[ thin ]
get "/" do
request = Rack::Request.new env
state = {
code: request["code"],
error: request["error"],
scope: request["scope"]
}
raise Signet::AuthorizationError, ("Authorization error: %s" % [ state[:error] ] ) if state[:error]
raise Signet::AuthorizationError, "Authorization code missing from the request" if state[:code].nil?
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: state[:code],
scope: state[:scope],
)
[
200,
{ "Content-Type" => "text/plain" },
"All seems to be OK. You can close this window and press ENTER in the application to proceed.",
]
end
end
Thread.current[:server].run!
}
end
# Returns user credentials for the given scope. Requests authorization
# if requrired.
def user_credentials_for(scope, user_id = 'default')
client_id = Google::Auth::ClientId.new(ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'])
token_store = Google::Auth::Stores::FileTokenStore.new(:file => ENV['GOOGLE_CREDENTIAL_STORE'])
port = 6969
redirect_uri = "http://localhost:#{port}/"
authorizer = Google::Auth::UserAuthorizer.new(client_id, scope, token_store, redirect_uri)
credentials = authorizer.get_credentials(user_id)
if credentials.nil? then
server_thread = run_local_server(authorizer, port, user_id)
url = authorizer.get_authorization_url
$stderr.puts ""
$stderr.puts "-----------------------------------------------"
$stderr.puts "Requesting authorization for '#{user_id.yellow}'"
$stderr.puts "Open the following URL in your browser and authorize the application."
$stderr.puts
$stderr.puts url.yellow.bold
$stderr.puts
$stderr.puts "⚠️ If you are authorizing on a different machine, you will have to port-forward"
$stderr.puts "so your browser can reach #{redirect_uri.yellow}"
$stderr.puts
$stderr.puts "⚠️ If you get a " << "This site can't be reached".red << " error in the browser,"
$stderr.puts "just copy the failing URL below. Copy the whole thing, starting with #{redirect_uri.yellow}."
$stderr.puts "-----------------------------------------------"
code = $stdin.readline.chomp
server_thread[:server].stop!
server_thread.join
credentials = authorizer.get_credentials(user_id)
# If the redirect failed, the user must have provided us with a code on their own
if credentials.nil? then
begin
require 'uri'
require 'cgi'
code = CGI.parse(URI.parse(code).query)['code'][0]
rescue StandardException
# Noop, if we could not get a code out of the URL, maybe it was
# not the URL but the actual code.
end
credentials = authorizer.get_and_store_credentials_from_code(
user_id: user_id,
code: code,
scope: scope,
)
end
end
credentials
end
credentials = user_credentials_for(['https://www.googleapis.com/auth/drive.readonly'])
In short, we run a web server expecting the redirect from the browser. It takes the code the browser sent, or it takes the code pasted by the user.
For headless Python scripts that need sensitive scopes, continuing to use run_console now produces the following (and the flow likely fails):
DeprecationWarning: New clients will be unable to use `InstalledAppFlow.run_console` starting on Feb 28, 2022. All clients will be unable to use this method starting on Oct 3, 2022. Use `InstalledAppFlow.run_local_server` instead. For details on the OOB flow deprecation, see https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html?m=1#disallowed-oob
The official solution is to migrate to a flow that spins up a local server to handle the OAuth redirect, but this will not work on remote headless systems.
The solution Google adopted in gcloud is to run a local server on the same machine as the user's browser and then have the user copy the redirect URL requested from this local server back to the remote machine. Note that this requires having gcloud installed both on the remote machine and on the user's workstation.
As a hack for situations where installing a script to echo back the redirect URL on the workstation is not practical, we can use a redirect URL that is guaranteed to fail and just have the user copy back the URL of the error page on which they will land after authorization is complete.
import urllib
from google_auth_oauthlib.flow import InstalledAppFlow
def run_console_hack(flow):
flow.redirect_uri = 'http://localhost:1'
auth_url, _ = flow.authorization_url()
print(
"Visit the following URL:",
auth_url,
"After granting permissions, you will be redirected to an error page",
"Copy the URL of that error page (http://localhost:1/?state=...)",
sep="\n"
)
redir_url = input("URL: ")
query = urllib.parse.urlparse(redir_url).query
code = urllib.parse.parse_qs(query)['code'][0]
flow.fetch_token(code=code)
return flow.credentials
scopes = ['https://www.googleapis.com/auth/drive.file']
flow = InstalledAppFlow.from_client_secrets_file(secrets_file, scopes)
credentials = run_console_hack(flow)
We could also ask the user to pass back the code query string parameter directly but that is likely to be confusing and error-prone.
The use of 1 as the port number means that the request is guaranteed to fail, rather than potentially hit some service that happens to be running on that port. (e.g. Chrome will fail with ERR_UNSAFE_PORT without even trying to connect)
"Hello world" for this error:
Generating an authentication URL
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
const {google} = require('googleapis');
const oauth2Client = new google.auth.OAuth2(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// generate a url that asks permissions for Blogger and Google Calendar scopes
const scopes = [
'https://www.googleapis.com/auth/blogger',
'https://www.googleapis.com/auth/calendar'
];
const url = oauth2Client.generateAuthUrl({
// 'online' (default) or 'offline' (gets refresh_token)
access_type: 'offline',
// If you only need one scope you can pass it as a string
scope: scopes
});
If something goes wrong the first step is to Re Check again the three values of the google.auth.OAuth2 function.
1 of 2
Compare to the store values under Google APIs console:
YOUR_CLIENT_ID
YOUR_CLIENT_SECRET
YOUR_REDIRECT_URL -
For example http://localhost:3000/login
2 of 2 (environment variables)
A lot of times the values store inside .env. So re-check the env and the output under your files - for example index.ts (Even use console.log).
.env
# Google Sign-In (OAuth)
G_CLIENT_ID=some_id_1234
G_CLIENT_SECRET=some_secret_1234
PUBLIC_URL=http://localhost:3000
index
const auth = new google.auth.OAuth2(
process.env.G_CLIENT_ID,
process.env.G_CLIENT_SECRET,
`${process.env.PUBLIC_URL}/login`
);
SUM:
Something like this will not work
const oauth2Client = new google.auth.OAuth2(
"no_such_id",
"no_such_secret",
"http://localhost:3000/i_forgot_to_Authorised_this_url"
);
I've fixed this problem with recreate my App in google console. And I think the problem was with redirect_url. I had this problem when I was using 'Android' type of App in google console (in this case you can't configure redirect url). In my android App I'm using google auth with WebView so the best option here use use 'Web' type for your app in google console.
In my case, had to update plugins. by running following command-
bundle exec fastlane update_plugins
With this redirect uri was getting created properly as
https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com&include_granted_scopes=true&redirect_uri=http://localhost:8081&response_type=code&scope=https://www.googleapis.com/auth/cloud-platform&state=2ce8a59b2d403f3a89fa635402bfc5c4
steps.oauth.v2.invalid_request 400 This error name is used for multiple different kinds of errors, typically for missing or incorrect parameters sent in the request. If is set to false, use fault variables (described below) to retrieve details about the error, such as the fault name and cause.
GenerateAccessToken GenerateAuthorizationCode
GenerateAccessTokenImplicitGrant
RefreshAccessToken
Google Oauth Policy

how to get the sent emails count from my account using gmail api?

iam using gmail api, in my rails application to send emails. a user can authenticate to google and send emails(it will ask for user consent. upon user approval he can send emails from his account)
my requirement is i want to show the logined user, how many emails sent from his email id in my rails app. for that im using using the below end point. but im getting an error
require 'net/http'
require 'uri'
in controler
def sent_email_count
_
api_key = "api_key_contains_smal_case_capital_case_letters_and_special_symbols"
uri = URI.parse("https://www.googleapis.com/gmail/v1/users/#{current_user.email}/messages?labelIds=SENT&q=newer_than%3A1d&key={api_key}")
#gmail_response = Net::HTTP.get_response(uri)
end
in views :-
response <%= #gmail_response >
but getting unauthorized error.
sent email count :- #Net::HTTPUnauthorized:0x00007f6f5e3e2158
i tried like below also. but its not working.(string interpoltion changes for api key)
uri = URI.parse("https://www.googleapis.com/gmail/v1/users/#{current_user.email}/messages?labelIds=SENT&q=newer_than%3A1d&key=#{api_key}")
#gmail_response = Net::HTTP.get_response(uri)
uri = URI.parse("https://www.googleapis.com/gmail/v1/users/#{current_user.email}/messages?labelIds=SENT&q=newer_than%3A1d&key=api_key")
#gmail_response = Net::HTTP.get_response(uri)
can some one help me with this
Answer
You are missing the token in your code, that's why your requests are HTTP 401 Unauthorized, I strongly recommend you to use the Official documentation Quickstart In your case, you should use the list_user_messages method.
First of all list all the messages using the q parameter as in:sent which means read all the sent messages from my Gmail and then count your array of messages. Here's an example:
# ...
# Previous quickstart code
user_id = "email#example.com"
result = service.list_user_messages(user_id=user_id, q="in:sent")
puts "count : #{result.messages.length()}\n\n"
Reference
Ruby Google API Client
Method: users.messages.list

Cannot get oAuth2 Access Token for Google Analytics API

I am using Rails + Garb Gem (Sija Branch) + omniauth-google-oauth2 Gem and I can successfully authenticate with the Google Analytics API and extract data that our app is generating when using a user login, e.g.:
Garb::Session.login('USERNAME', '<PASSWORD>')
I can then use Garb to connect to the Analytics Profile I want and pull the data from it and display some charts on a webpage. This all works fine.
However, I want to use oAuth2 to authenticate with Analytics which is why I had to install the Sija branch of the Garb Gem from Github (it supports oAuth2) and I also installed the omniauth-google-oauth2 Gem. Now in theory I should be able to authenticate using the following code:
Garb::Session.access_token = access_token # an instance of OAuth2::Client
It's at this point that it gets a little hazy for me and I would greatly appreciate some guidance. Here's how far I have gotten:
1) I created a Project in the Google API console and turned on Analytics API under Services
2) This provided me with a Client ID and Client Secret
3) I came across this code which I could populate with the ID and Secret above:
client = OAuth2::Client.new(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => '/o/oauth2/auth',
:token_url => '/o/oauth2/token'
})
4) Then there is the next bit of code:
response = OAuth2::AccessToken.new(
client,
STORED_TOKEN, {
refresh_token: STORED_REFRESH_TOKEN,
expires_at: STORED_EXPIRES_AT
})
5) and then in theory connect with:
Garb::Session.access_token = response
The problem I have is I don't have the token information in Point (4) above. It seems to me that with oAuth2 I need to do a "handshake" once and print out the return token values? Perhaps through Rails code which prints the values returned out and then paste the token values into a constant in the Rails app so that I can use them in the above code? I really am confused. As I mentioned earlier, the web app works fine using the user login authentication. All the web app is doing is authenticating with analytics, pulling down some data and drawing a chart. But I am stuck converting it over to oAuth2 as I just do not know how to get the Access Token that the Garb Gem is looking for. I should also note that this is not a public website with multiple users authenticating, this is a CMS website that is connecting to our own Analytics data.
I have seen some partial snippets of aspects of this but not a fully explained or working example. I would really appreciate any guidance and help with this question.
Many thanks in advance,
JR
I've soldiered through this over the last few weeks, so let me share what worked:
To use Oauth2 you need to get a 'refresh token' that you use to 're-authenticate' with google each time you make an API call. The steps for this are as follows:
1) Setup your account in the API console - https://code.google.com/apis/console/b/0/ (seems like you've done that well)
2) In your API account, make sure you have a redirect URI pointing back to your application:
http://some-url.com/auth/google_oauth2/callback
http://localhost:3000/auth/google_oauth2/callback
Note here that google won't let you call back to your local machine as 0.0.0.0:3000... so you'll need to use localhost explicitly
3) In your route file, tie that redirect url to an action in the controller where you're going to create the project or authentication
match '/auth/:provider/callback' => 'authentications#create'
The ':provider' simply lets you match on multiple types of oauth, but you could just put 'google_oauth2' there as well.
4) Now create that action in your controller
def create
auth = request.env["omniauth.auth"]
params = request.env["omniauth.params"]
project = Project.find(params['project_id'])
Authentication.create(:project_id => project.id, :provider => auth['provider'], :uid => auth['uid'], :access_token => auth['credentials']['refresh_token'])
flash[:notice] = "Authentication successful."
redirect_to owner_view_project_path(project)
end
5) The controller action should retrieve the relevant fields from the response object (details of response object here: https://github.com/zquestz/omniauth-google-oauth2) - in particular, you need to get the 'refresh_token' and save that to your project or authentication object - if you haven't added an 'access_token' attribute to the desired object, go do that now with a migration, then start saving the refresh token to that attribute
6) Now when you're ready to call that particular authentication and get API data for it, you can load up that object where you saved the access token, and use that to get a new session with the google API as follows:
#authentication = Authentications.find(params[:id])
client = OAuth2::Client.new GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET,
{
:site => 'https://accounts.google.com',
:authorize_url => "/o/oauth2/auth",
:token_url => "/o/oauth2/token",
}
response = OAuth2::AccessToken.from_hash(client, :refresh_token => #authentication.access_token).refresh!
Garb::Session.access_token = response
#profiles = Garb::Management::Profile.all
What this code did was create an OAuth2 access token (response) by specifying the client and then a refresh_token, then calling 'refresh!' to get a refreshed access token... then use that access token to establish your Garb session, then call down all the profiles for a given account using the Gard::Management::Profile.all
Hope this helps - let me know if you have questions!
Just a note on what worked for me in:
For steps 3, 4 & 5 I used cURL instead to retrieve the Access/Refresh token. Step 6 is then the same for me (using the Sija branch of the Garb Gem). So using cURL:
Using the details associated with your Google app POST the following using cURL:
curl --data "code=<APP_CODE>&redirect_uri=http://localhost:3000/oauth2callback&client_id=<CLIENT_ID>.apps.googleusercontent.com&scope=&client_secret=<CLIENT_SECRET>&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token
The response takes the form:
{
"access_token" : "<ACCESS_TOKEN>",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "<REFRESH_TOKEN>"
}
which you can plug into the Garb Gem as per part 6.
The answer by #CamNorgate is valid.
If you don't have a "refresh_token" back from Omniauth on the callback make sure you are correctly initializing :google_oauth2
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"],
{ :scope=>"https://www.google.com/m8/feeds, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile",
:approval_prompt=>"force", access_type="offline"
}
end
Make sure to include :approval_prompt=>"force", access_type="offline" in order for the refresh_token to be sent back. The refresh_token is only provided on the first authorization from the user.

How to authenticate to flickr with Flickraw gem

I want to upload a photo but need to authenticate with flickr in order to do so. I am using the flickraw gem but don't understand the instructions below:
require 'flickraw'
FlickRaw.api_key="... Your API key ..."
FlickRaw.shared_secret="... Your shared secret ..."
token = flickr.get_request_token(:perms => 'delete')
auth_url = token['oauth_authorize_url']
puts "Open this url in your process to complete the authication process : #{auth_url}"
puts "Copy here the number given when you complete the process."
verify = gets.strip
begin
flickr.get_access_token(token['oauth_token'], token['oauth_token_secret'], verify)
login = flickr.test.login
puts "You are now authenticated as #{login.username}"
rescue FlickRaw::FailedResponse => e
puts "Authentication failed : #{e.msg}"
end
Can someone explain to me what this code is doing and how I should use it.
First , you should open http service
rails server
On the Console , you will see
Open this url in your process to complete the authication process : http://xxxx.xxxx.xxxx.xxxx........
you have to copy the url and post it on your browser.
After log in , you will get a number , like
xxx-xxx-xxx
just copy it onto your console!
Create a new Flickr app. Get the api key and shared secret from there.
"flickr.get_request_token" creates a request oauth token from flickr. You might want to set permissions to :write if you want to upload instead of :delete
auth_url is where you have to redirect to. That url also contains the oauth request tokens that you just created.
Once you are in auth_url page ( for this you have to login to your Yahoo! account), you can authorize your app to access your flickr account. This gives a verification id.
Use that verification id to you can get the oauth access tokens using this method call 'flickr.get_access_token'
Once you have the Oauth access tokens, you could do any api queries on flickr that your :perms would allow.
The entire process is described in detail here - http://www.flickr.com/services/api/auth.oauth.html
I submitted a pull request but here is an updated form of the documentation that should make this more clear
== Simple
+#Place near the top of your controller i.e. underneath FlickrController < ApplicationController
require 'flickraw'
+#Create an initializer file i.e. Flickr.rb and place it in config -> initializers folder
FlickRaw.api_key="... Your API key ..."
FlickRaw.shared_secret="... Your shared secret ..."
+#Examples of how the methods work
list = flickr.photos.getRecent
id = list[0].id
...

Rails EOF Error when using HTTP.get_response to retrieve Facebook access token

I trying to implement a Login with Facebook feature on my site, and hitting a roadblock trying to get the access token back from Facebook. Here is my code:
if params[:error_reason] == "user_denied" then
flash[:error] = "To login with Facebook, you must click 'Allow' to let the site access your information"
redirect_to :login
elsif params[:code] then
token_uri = URI.parse("https://graph.facebook.com/oauth/access_token?client_id=****************&redirect_uri=http://localhost:3000/auth/fblogin&client_secret=***************&code="+URI.escape(params[:code]))
response = Net::HTTP.get_response(token_uri)
session[:response] = response
data = ActiveSupport::JSON.decode(response)
access_token = data[:access_token]
flash[:error] = access_token
redirect_to :register
end
This is inside a fblogin controller function that is the target of the initial redirect to get an authorization code (the params[:code]).
But when I run through this, I get the following error:
EOFError in AuthController#fblogin
on the Net::HTTP.get_response(token_uri) line. I've searched all over, and can't find anything to indicate what this means. Could it be the obscure characters Facebook uses in their access tokens? I'm totally lost!
You are receiving an EOFError because you are trying to connect to an https URL using code that only works with http. See the section entitled "SSL/HTTPS request" at this Net::HTTP Cheat Sheet for the basics.
However, I would recommend using a third-party library to manage this for you, such as OAuth2 for utilizing Facebook's OAuth2 API, where you'd write code like this:
def client
OAuth2::Client.new('app_id', 'app_secret', :site => 'https://graph.facebook.com')
end
# in your callback code:
access_token = client.web_server.get_access_token(params[:code], :redirect_uri => 'http://localhost:3000/auth/fblogin')
user = JSON.parse(access_token.get('/me'))
If you really want to make the requests yourself, you can look at libraries like Faraday to execute the HTTPS requests for you.

Resources