Yahoo API contacts (Authentication , authorization & API GET request)
This document is created by datnt
1> Register an Application with Yahoo
2> Setup consumer_id & secret to our application
3> CHAPTER 1: Begin AUTHENTICATION & AUTHORIZATION WITH YAHOO
3.1> STEP 1: send request to retrieve: oauth_token & xoauth_request_auth_url
nonce = SecureRandom.hex()
ts=Time.now.to_i
q = URI.encode_www_form(
"oauth_nonce" => nonce,
"oauth_timestamp" => ts,
"oauth_consumer_key" => YAHOO_CONFIG[:client_id],
"oauth_signature" => "#{YAHOO_CONFIG[:client_secret]}&",
"oauth_signature_method" => "plaintext",
"oauth_version" => "1.0",
"xoauth_lang_pref" => "en-us",
"oauth_callback" => "http://localhost:3000/auth/yahoo/callback"
)
response = RestClient.get "https://api.login.yahoo.com/oauth/v2/get_request_token?#{q}"
p=CGI.parse(response)
oauth_token = p["oauth_token"].first
session[:yahoo_oauth_token] = oauth_token
oauth_token_secret = p["oauth_token_secret"].first
session[:yahoo_oauth_token_secret] = oauth_token_secret
xoauth_request_auth_url = p["xoauth_request_auth_url"]
NOTE: Take care with the sign “&” and “plaintext” which I colored in Red color above.
3.2> STEP 2: you will need to open a popup on web browser to let end-user authenticating (by email&password) with yahoo. The url for popup windows is retrieved from STEP-1 above (it is p["xoauth_request_auth_url"]):
a) Javascript code function to open popup
function popupCenter(url, width, height, name) {
var left = (screen.width / 2) - (width / 2);
var top = (screen.height / 2) - (height / 2);
width = screen.width / 2;
height = screen.height / 2;
return window.open(url, name, "menubar=no,scrollbars=yes,toolbar=no,status=no,width=" + width + ",height=" + height + ",left=" + left + ",top=" + top);
}
b) Calling to function popup:
popupCenter(yahoo_url, 400, 400, "Yahoo");
NOTE 1(again): yahoo_url is p["xoauth_request_auth_url"] from STEP1
NOTE 2: Based on my code, yahoo will callback to “http://localhost:3000/auth/yahoo/callback”. I had already defined this route in routes.rb
You can see from the routes above, yahoo will callback to controller “authenticaions”, at action “create”
3.3> Receive the callback triggered from yahoo.com, at controller “authenticaions”, at action “create”:
NOTE: by default, you have object name “request” at this action.
So, at this step, you will have “oauth_verifier” and “provider”.
session[:oauth_verifier] = request["oauth_verifier"]
session[:yahoo_provider] = request["provider"]
NOTE: the parameter oauth_verifier means end user had allowed your web application to access end_user’s yahoo contacts
3.4> Now, you can send another request to yahoo, to retrieve the access_token (yahoo called it oauth_token).
NOTE: also, within controller “authenticaions”, at action “create”, you insert the code below:
nonce = SecureRandom.hex()
ts=Time.now.to_i
q = URI.encode_www_form(
"oauth_nonce" => nonce,
"oauth_timestamp" => ts,
"oauth_consumer_key" => YAHOO_CONFIG[:client_id],
"oauth_signature" => "#{YAHOO_CONFIG[:client_secret]}&#{session[:yahoo_oauth_token_secret]}",
"oauth_signature_method" => "plaintext",
"oauth_version" => "1.0",
"xoauth_lang_pref" => "en-us",
"oauth_token" => session[:yahoo_oauth_token],
"oauth_verifier" => session[:oauth_verifier]
)
response = RestClient.get "https://api.login.yahoo.com/oauth/v2/get_token?#{q}"
p=CGI.parse(response)
session[:yahoo_access_token] = p["oauth_token"].first
session[:yahoo_access_token_secret] = p["oauth_token_secret"].first
NOTE: be careful with sign “&” and “plaintext” I colored with Red color above. You also take note that, “oauth_signature” takes another value with another format from the previous request to Yahoo.
3.5> End of chapter 1:
Now, you have yahoo access token, you can send request to query end_user contacts. But, the tragedy is not over yet.
END OF CHAPTER 1.
4> CHAPTER 2: Send request to Yahoo API to query for end_user contacts:
Before begin with coding, we need some foundation knowledge. I describe line by line below:
a) A short note from yahoo:”However, calls made to actual Yahoo APIs are sent insecurely over HTTP and thus require HMAC-SHA1 signatures” (http://developer.yahoo.com/oauth/guide/oauth-requesttoken.html#). I urge you to open the yahoo link I provided here and ready through everything, if you do not have time, learn by heart these words “require HMAC-SHA1 signatures”
b) A hint from (http://nullinfo.wordpress.com/oauth-yahoo/): I urge you to read through everything. But the most important paragraph is at “A Signature Base String is a RFC 3986 encoded concatenation of” and “A Signature Key is the concatenation of:”
c) Another hint, (http://stackoverflow.com/questions/4524911/creating-signature-and-nonce-for-oauth-ruby), open that stackoverflow page, you will see these words “this gist by erikeldridge on github”. It is posted by iain , answered on Jan 21, 2011. Open that link, you will see 2 files, “oauth_util.rb” and “example.rb”
5> Coding the query to send to yahoo:
Now, you may want to apply the above knowledge and write the code by your self, or continue reading and make some experiment with me:
STEP 1:
- Using the TOKEN & TOKEN SECRET which you had received.
- Adding more library to your authentications_controller, if needed.
require 'uri'
require 'cgi'
require 'openssl'
require 'base64'
require 'net/http'
STEP 2 (add more code into action create of authentications_controller.rb)
consumer_key = YAHOO_CONFIG[:client_id]
consumer_secret = YAHOO_CONFIG[:client_secret]
token = "#{session[:yahoo_access_token]}"
token_secret = "#{session[:yahoo_access_token_secret]}"
req_method = "GET"
sig_method = "HMAC-SHA1"
oauth_version = "1.0"
callback_url = ""
STEP 3(add more code into action create of authentications_controller.rb
url = "http://social.yahooapis.com/v1/user/me/contacts"
parsed_url = URI.parse( url )
Note for step3: I will declare some non-sense behaviors of Yahoo API URL at the end of this document
STEP 4(add more code into action create of authentications_controller.rb
nonce = Array.new( 5 ) { rand(256) }.pack('C*').unpack('H*').first
STEP 5: Define an action within file authentications_controller.rb
def percent_encode( string )
# ref http://snippets.dzone.com/posts/show/1260
return URI.escape( string, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") ).gsub('*', '%2A')
end
STEP 6: Define another action within file authentications_controller.rb
def signature(consumer_secret, token_secret, base_str)
key = percent_encode( consumer_secret ) + '&' + percent_encode( token_secret )
# ref: http://blog.nathanielbibler.com/post/63031273/openssl-hmac-vs-ruby-hmac-benchmarks
digest = OpenSSL::Digest::Digest.new( 'sha1' )
hmac = OpenSSL::HMAC.digest( digest, key, base_str )
# ref http://groups.google.com/group/oauth-ruby/browse_thread/thread/9110ed8c8f3cae81
Base64.encode64( hmac ).chomp.gsub( /\n/, '' )
end
STEP 7: Define another action within file authentications_controller.rb
def query_string(params)
pairs = []
params.sort.each { | key, val |
pairs.push( "#{ percent_encode( key ) }=#{ percent_encode( val.to_s ) }" )
}
pairs.join '&'
end
STEP 8:(add more code into action create of authentications_controller.rb)
params = {
'format' => 'json',
'oauth_consumer_key' => consumer_key,
'oauth_nonce' => nonce,
'oauth_signature_method' => sig_method,
'oauth_token' => token, #DATNT: ADDED THIS PARAMETER
'oauth_timestamp' => Time.now.to_i.to_s,
'oauth_version' => oauth_version
}
if parsed_url.query
params.merge! CGI.parse( parsed_url.query )
end
req_url = parsed_url.scheme + '://' + parsed_url.host + parsed_url.path
base_str = [
req_method,
percent_encode( req_url ),
# normalization is just x-www-form-urlencoded
percent_encode( query_string(params) )
].join( '&' )
params[ 'oauth_signature' ] = signature(consumer_secret, token_secret, base_str)
datnt_query_string= query_string (params)
STEP 9: Send and receive the data
yahoo_response=nil
Net::HTTP.start( parsed_url.host ) { | http |
req = Net::HTTP::Get.new "#{ parsed_url.path }?#{ datnt_query_string }"
response = http.request(req)
yahoo_response = response
response.read_body
}
STEP 10:
a) If you request json, then you parse json
yahoo_json = JSON.parse(yahoo_response.read_body)
b) If you request json, but you receive xml (yeah, yahoo did return xml, when I requested for json), then parse xml
my_string = yahoo_response.read_body.force_encoding("utf-8")
doc = Nokogiri::XML(my_string)
APPENDIX:
Nonsense behaviors of yahoo api url:
No 1>
Reference: http://developer.yahoo.com/social/rest_api_guide/contact-resource.html#ContactObject
- Yahoo provide you this api uri: http://social.yahooapis.com/v1/user/me/contacts?format=json
But when you use this uri to Step 3 about, combine with removing “format” => “json” at Step 8, yahoo will send you xml.
That’s why I have to put “format” => “json” at step 8, and remove “?format=json” from the uri
No 2>
Happens with YQL, refer to: http://developer.yahoo.com/yql/console/
- If you enter query “select * from social.contacts where guid = me”
you will get this uri:
Actually, when I used this query for Step 3 above, Yahoo returns to me an error message contained within an xml file
And this is it: http://developer.yahoo.com/forum/YQL/Strange-sample-query-object-Object-/1311985846289-4c7a5f1e-d4b5-49ff-b354-1056f9245fa3
“Syntax error(s) [line 1:0 no viable alternative at character '[',line 1:14 no viable alternative at character ']']>”
No 3>
Happens with YQL, refer to: http://developer.yahoo.com/yql/console/
- If you enter query “select * from social.contacts where guid = me” ,and choose the JSON format
you will get this uri:
What you will get when using this as url for Step 3 is
+ Success, you received response code 200 (which mean everything fine)
+ And, there is no data at all, there is no response data at all
No 4>
Many times, yahoo returns its json data on its YQL console , or API REST, but the real response data format is different with what it is showing on its document.