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

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

Related

What auth flow to use with spa and service account msal

There's so many different flows in the Microsoft docs that I have no clue what one is needed for me. I am using React and Python. (I understand node, so if someone explains using node/express its fine)
What user should see:
A page with a button to login, nav is there but wont work till logged in. The login creates a popup to sign in with Microsoft account. Once signed in, the user will be able to use nav to see dynamics information.
What I am trying to do:
This app needs to sign in a user and obtain the users email through 'https://graph.microsoft.com/v1.0/me'.(no client secrets needed) Then I need to send that email in this request;
(The tenant == {company}.crm.dynamics.com.)
allInfo = requests.get(
f'https://{TENANT}api/data/v9.0/company_partneruserses?$filter=company_email eq \'{email}\'', headers=headers).json()
This backend request needs to have a client secret to obtain the information. So I believe my backend also needs to be logged on to a service account. I believe I need to get a token for my backend to make requests on behalf of the service account.
What I have:
I have a React frontend that is signing a user in and calling 'https://graph.microsoft.com/v1.0/me' correctly and getting that email. Once I get the email, I am sending it to my backend.
Now I have no clue how to proceed and have tried many things.
What I have tried for backend:
Attempt 1: I get a token but error: {'error': {'code': '0x80072560', 'message': 'The user is not a member of the organization.'}}. Problem is, this id is the Azure AD ID. It should def work
#app.route('/dynToken', methods=['POST'])
def get_dyn_token():
req = request.get_json()
partnerEmail = req['partnerEmail']
token = req['accessToken']
body = {
"client_id": microsoft_client_id,
"client_secret": client_secret,
"grant_type": "client_credentials",
"scope": SCOPE_DYN,
}
TENANTID = '{hash here}'
res = requests.post(
f'https://login.microsoftonline.com/{TENANTID}/oauth2/v2.0/token', data=body).json()
dyn_token = res['access_token']
headers = {
"Prefer": "odata.include-annotations=\"*\"",
"content-type": "application/json; odata.metadata=full",
"Authorization": f"Bearer {dyn_token}"
}
try:
allInfo = requests.get(
f'https://{TENANT}api/data/v9.0/company_partneruserses?$filter=company_email eq \'{email}\'', headers=headers).json()
print(allInfo)
Attempt 2:
Same code but instead of f'https://login.microsoftonline.com/{TENANTID}/oauth2/v2.0/token' its
f'https://login.microsoftonline.com/common/oauth2/v2.0/token'. Error: An exception occurred: [Errno Expecting value] : 0. Because it returns an empty string.
Now I don't know if I am even on the right path or where to go. I know the routes work themselves if the token is correct. I used only SSR with no react and these routes work. But I need the React to be there too. I just don't know what flow to use here to get what I need. The docs make it easy for /me route to work. But the {company}crm.dynamics.com docs don't really provide what I am trying to do.
Additional info after comment:
What 'f'https://{TENANT}api/data/v9.0/company_partneruserses?$filter=company_email eq '{email}'', headers=headers" is trying to get are API keys. Full code :
try:
allInfo = requests.get(
f'https://{TENANT}api/data/v9.0/company_partneruserses?$filter=company_email eq \'{email}\'', headers=headers).json()
partner_value = allInfo['value'][0]['_company_partner_value']
response = requests.get(
f'https://{TENANT}api/data/v9.0/company_partnerses({partner_value})', headers=headers).json()
return {'key': response['company_apikey'], 'secret': response['company_apisecret']}
Then once it has the keys:
def api_authentication(apikey, apisecret):
headers = get_headers() #<-- same headers as above with using dyn_token
response = requests.get(
f'https://{TENANT}api/data/v9.0/company_partnerses?$filter=company_apikey eq \'{apikey}\' and company_apisecret eq \'{apisecret}\'&$select=company_apikey,company_apisecret,_company_account_value,_company_primarycontact_value,blahblah_unassignedhours,company_reporturl', headers=headers).json()
return response
Afterwards I am able to get all the information I am looking for to send back to my frontend for the client to see. (By making multiple request to crm with these keys)
The client_credentials grant that you are using should work, provided the CRM trusts the token issued to the client (your python backend). Please use MSAL library instead of hand crafting the token request. It will save you time and eliminate errors.

Unautorized API calls JwtCreator

I'm playing around with the DocuSign's Ruby Quickstart app and I've done the following:
have an Admin account
have an organization
created an Integration(Connected App) for which I've granted signature impersonation scopes in the Admin Dashboard(made RSA keys, put callback urls, etc)
even if I've done the above, I've also made the request to the consent URL in a browser: SERVER/oauth/auth?response_type=code &scope=signature%20impersonation&client_id=CLIENT_ID &redirect_uri=REDIRECT_URI
Integration appears to have everything enabled
Then in the JwtCreator class the check_jwt_token returns true, updates account info correctly.
But when I try the following(or any other API call):
envelope_api = create_envelope_api(#args)
options = DocuSign_eSign::ListStatusChangesOptions.new
options.from_date = (Date.today - 30).strftime('%Y/%m/%d')
results = envelope_api.list_status_changes #args[:account_id], options
The api call raises an exception with DocuSign_eSign::ApiError (Unauthorized):
Args are:
#args = {
account_id: session[:ds_account_id],
base_path: session[:ds_base_path],
access_token: session[:ds_access_token]
}
All with correct info.
What am I missing?
For clarity, I was using some classes from the Quickstart app(like JwtCreator, ApiCreator, etc) along my code.
Not sure at this point if it's my mistake or part of the Quickstart app but this call:
results = envelope_api.list_status_changes #args[:account_id], options
the account_id was something like this "82xxxx-xxxx-xxxx-xxxx-xxxxxxxx95e" and I was always getting Unauthorized responses.
On a medium.com tutorial the author used the 1xxxxxx account_id and with this form, it worked.

Trouble authenticating with Google Content API for Shopping

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.

Linkedin Oauth with Rails

We have been connecting to Linkedin for awhile now successfully. However, we get some errors from time to time and I'm hoping someone can help shed some light on this. Here's our code:
def linkedin_login
request_token = Linkedin.client.request_token(oauth_callback: "http://#{SITE_URL}/linkedin/auth/")
session[:linkedin_request_token] = request_token.token
session[:linkedin_request_secret] = request_token.secret
redirect_to request_token.authorize_url
end
def linkedin_auth
raise "Don't have proper session or oauth_verifier" if session[:linkedin_request_token].blank? or session[:linkedin_request_secret].blank? or params[:oauth_verifier].blank?
access_token = Linkedin.client.authorize_from_request(session[:linkedin_request_token], session[:linkedin_request_secret], params[:oauth_verifier])
raise "Nil access token" if access_token.blank?
redirect_to linkedin_process_path(token: access_token.first, secret: access_token.second)
end
We're hitting the "raise 'Don't have proper session or oauth_verifier'" more than I would expect. When looking at the ENV for the errors, those people don't have the session values set from the original method. We have before_filters set on the application controller so initialize the session, so I know it's active.
My next thought was whether "request_token" was generating a value request_token, and I've tried many times and they all bring something back. We get many of these a day. After the error, if the user tries again, it works fine, which is why I'm so confused.
any thoughts on what could cause this?
Based on your code, it looks like you're making the request token call every time the user logs into your application. That's not the proper method to authenticate. You really only need to fetch the request token once, then use that to upgrade for an access token (as you're doing in your linkedin_auth method). From there, just save the access token and secret in your DB and fetch it anytime you need to make an API call for that particular user.
Our authentication is described more in detail here: https://developer.linkedin.com/documents/authentication
Also, this is just a personal preference, but I like using the OAuth gem for Rails as opposed to using a LinkedIn wrapper. It's easy to use and light weight.
Just as an example, you could do your auth this way:
require 'oauth'
def auth
api_key = 'XXXXXXXXX'
api_secret = 'XXXXXXXXX'
configuration = { :site => 'https://api.linkedin.com',
:authorize_path => 'https://www.linkedin.com/uas/oauth/authenticate',
:request_token_path => 'https://api.linkedin.com/uas/oauth/requestToken',
:access_token_path => 'https://api.linkedin.com/uas/oauth/accessToken' }
consumer = OAuth::Consumer.new(api_key, api_secret, configuration)
#Request token
request_token = consumer.get_request_token
# Output request URL to console
puts "Please visit this URL: https://api.linkedin.com/uas/oauth/authenticate?oauth_token=" + request_token.token + " in your browser and then input the numerical code you are provided here: "
# Set verifier code
verifier = $stdin.gets.strip
# Retrieve access token object
#access_token = request_token.get_access_token(:oauth_verifier => verifier)
end
You would only need to invoke this method when the user first authorizes your app. Save their access token then use it for subsequent API calls. Note, my example makes use of the console to enter the PIN verifier. In a real world example you'd want to programmatically save the PIN in a session variable or in memory, then use it to get the access token.

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
...

Resources