hello.js
hello.js copied to clipboard
Yahoo via OAuth2
Hi,
I have done it myself for Yahoo via its new OAuth2 (https://developer.yahoo.com/oauth2/guide/#implicit-grant-flow-for-client-side-apps).
The Authentication is working just fine, and can get you the access token. However, the Contact API is not working as Yahoo server is not returning the correct Access-Control-Allow-Origin
header cause the browser to block the Cross-Origin
xhr call.
So the only solution was to take the Access Token and follow the rest via CURL (where it works just fine).
CONCEPTS:
-
Creating API Access keys from Yahoo.com will require you to insert
.com
in your localhost. So make a vhost that has.com
-
Yahoo's OAuth server will not accept such large URL and hence return an error the details of which are not so listed anywhere. So except for
state>callback
parameter, rest all are passed to the processing page via Session and reinserted into hello.js upon successful authentication.
Code: Modified modules/yahoo.js
//
// Yahoo
//
// Register Yahoo developer
(function(hello){
function formatError(o){
if(o && "meta" in o && "error_type" in o.meta){
o.error = {
code : o.meta.error_type,
message : o.meta.error_message
};
}
}
function formatFriends(o,headers,request){
//alert(dump(headers));
formatError(o);
paging(o, headers, request);
var contact,field;
if(o.query&&o.query.results&&o.query.results.contact){
o.data = o.query.results.contact;
delete o.query;
if(!(o.data instanceof Array)){
o.data = [o.data];
}
for(var i=0;i<o.data.length;i++){
contact = o.data[i];
contact.id = null;
for(var j=0;j<contact.fields.length;j++){
field = contact.fields[j];
if(field.type === 'email'){
contact.email = field.value;
}
if(field.type === 'name'){
contact.first_name = field.value.givenName;
contact.last_name = field.value.familyName;
contact.name = field.value.givenName + ' ' + field.value.familyName;
}
if(field.type === 'yahooid'){
contact.id = field.value;
}
}
}
}
return o;
}
function paging(res, headers, request){
// PAGING
// http://developer.yahoo.com/yql/guide/paging.html#local_limits
if(res.query && res.query.count && request.options ){
res['paging'] = {
next : '?start='+ ( res.query.count + ( +request.options.start || 1 ) )
};
}
}
var yql = function(q){
return 'https://query.yahooapis.com/v1/yql?q=' + (q + ' limit @{limit|100} offset @{start|0}').replace(/\s/g, '%20') + "&format=json";
};
hello.init({
'yahoo' : {
// Ensure that you define an oauth_proxy
/*oauth : {
version : "1.0a",
//version : "2.0",
auth : "https://api.login.yahoo.com/oauth/v2/request_auth",
request : 'https://api.login.yahoo.com/oauth/v2/get_request_token',
token : 'https://api.login.yahoo.com/oauth/v2/get_token'
},*/
oauth: {
version : 2,
auth : "https://api.login.yahoo.com/oauth2/request_auth",
request : 'https://api.login.yahoo.com/oauth2/get_request_token',
token : 'https://api.login.yahoo.com/oauth2/get_token',
},
// Login handler
login : function(p){
// Change the default popup window to be atleast 560
// Yahoo does dynamically change it on the fly for the signin screen (only, what if your already signed in)
p.options.window_width = 560;
},
/*
// AUTO REFRESH FIX: Bug in Yahoo can't get this to work with node-oauth-shim
login : function(o){
// Is the user already logged in
var auth = hello('yahoo').getAuthResponse();
// Is this a refresh token?
if(o.options.display==='none'&&auth&&auth.access_token&&auth.refresh_token){
// Add the old token and the refresh token, including path to the query
// See http://developer.yahoo.com/oauth/guide/oauth-refreshaccesstoken.html
o.qs.access_token = auth.access_token;
o.qs.refresh_token = auth.refresh_token;
o.qs.token_url = 'https://api.login.yahoo.com/oauth/v2/get_token';
}
},
*/
base : "https://social.yahooapis.com/v1/user/",
get : {
"me" : yql('select * from social.profile(0) where guid=me'),
//"me/friends" : yql('select * from social.contacts where guid=me'),
"me/friends" : 'me/contacts',
"me/following" : yql('select * from social.contacts(0) where guid=me')
},
wrap : {
me : function(o){
formatError(o);
if(o.query&&o.query.results&&o.query.results.profile){
o = o.query.results.profile;
o.id = o.guid;
o.last_name = o.familyName;
o.first_name = o.givenName || o.nickname;
var a = [];
if(o.first_name){
a.push(o.first_name);
}
if(o.last_name){
a.push(o.last_name);
}
o.name = a.join(' ');
o.email = o.emails?o.emails.handle:null;
o.thumbnail = o.image?o.image.imageUrl:null;
}
return o;
},
// Can't get ID's
// It might be better to loop through the social.relationshipd table with has unique ID's of users.
"me/friends" : formatFriends,
"me/following" : formatFriends,
"default" : function(res){
paging(res);
return res;
}
},
xhr : function(p)
{
//alert(dump(p.options.access_token));
/*if( p.method !== 'get' && p.method !== 'delete' && !hello.utils.hasBinary(p.data) ){
// Does this have a data-uri to upload as a file?
if( typeof( p.data.file ) === 'string' ){
p.data.file = hello.utils.toBlob(p.data.file);
}else{
p.data = JSON.stringify(p.data);
p.headers = {
'Content-Type' : 'application/json'
};
}
}*/
if(typeof(p.options.access_token)!='undefined'){
p.headers = {
'Authorization' : 'Bearer '+p.options.access_token
};
}
return true;
},
}
});
})(hello);
Hi Raheel that's great. However I can't merge it without a pull request. Thanks
The reason I didnt create a pull-request is that this is partial solution as it will rely on Curl via PHP or any other code. The reason being that is though our work is complete, but the Yahoo server is not returning a crucial header that allows cross-browser ajax call.
That's fine, the oauth-proxy settings can easily be modified to enforce that a passthru requests.
Thanks On 5 Feb 2015 00:34, "Raheel Hsn" [email protected] wrote:
The reason I didnt create a pull-request is that this is partial solution as it will rely on Curl via PHP or any other code. The reason being that is though our work is complete, but the Yahoo server is not returning a crucial header that allows cross-browser ajax call.
— Reply to this email directly or view it on GitHub https://github.com/MrSwitch/hello.js/issues/192#issuecomment-72875905.
ok.. well the reason I used OAuth2.0 instead was that I didnt want to use the proxy.. Otherwise, OAuth1 via proxy would have just worked fine...
Also, tell me which branch do you want me to create the pull request?
Thanks..
The oauth-proxy is already doing CORS polyfills for other networks.
Git can merge any branch into MrSwitch/master so however you want to manage it.
Thanks
@Rah1x did you create a PR?
Hey, I just wanted to chime in and say that I noticed that the Yahoo Social APIs are now listed under the OAuth 2.0 Guide: https://developer.yahoo.com/oauth2/guide/ (March 10th, 2015). It wasn't there the last time I checked (March 6th 2015). So, maybe Yahoo's OAuth2 APIs for Contacts w/ implicit grant is fully working now i.e. no need for proxy anymore?
@newtonapple that's great
I just did a rudimentary test, i found an issue with the state parameter character limit,
i.e. this wont work
https://api.login.yahoo.com/oauth2/request_auth?client_id=dj0yJmk9cjVDdHlDaGtrbldJJmQ9WVdrOVYyZFhSWE4yTm04bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1jOA--&response_type=token&redirect_uri=http%3A%2F%2Flocal.knarly.com%2Fhello.js%2Fredirect.html&display=popup&state=%7B%22client_id%22%3A%22dj0yJmk9cjVDdHlDaGtrbldJJmQ9WVdrOVYyZFhSWE4yTm04bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1jOA--%22%2C%22network%22%3A%22yahoo%22%2C%22display%22%3A%22popup%22%2C%22callback%22%3A%22_hellojs_22urf8xp%22%2C%22state%22%3A%22%22%2C%22redirect_uri%22%3A%22http%3A%2F%2Flocal.knarly.com%2Fhello.js%2Fredirect.html%22%7D
but this will
https://api.login.yahoo.com/oauth2/request_auth?client_id=dj0yJmk9cjVDdHlDaGtrbldJJmQ9WVdrOVYyZFhSWE4yTm04bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1jOA--&response_type=token&redirect_uri=http%3A%2F%2Flocal.knarly.com%2Fhello.js%2Fredirect.html&display=popup&state=%7B%22client_id%22%3A%22dj0yJmk9cjVDdHlDaGtrbldJJmQ9WVdrOVYyZFhSWE4yTm04bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1jOA--%22%2C%22network%22%3A%22yahoo%22%2C%22display%22%3A%22popup%22%2C%22callback%22%3A%22_hellojs_22urf8xp%22
Reported to https://hackerone.com/reports/51106
+1 Awesome, looking forward to this!
Yet another critical issue with yahoo's OAuth2 state parameter...
The state field is formatted such that the encoded double quote character ("), aka (%22) is converted to a plus (+) on the return
e.g.
Request:
https://api.login.yahoo.com/oauth2/request_auth?client_id=dj0yJmk9cjVDdHlDaGtrbldJJmQ9WVdrOVYyZFhSWE4yTm04bWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD1jOA--&response_type=token&redirect_uri=http%3A%2F%2Flocal.knarly.com%2Fhello.js%2Fredirect.html&display=popup&state=%7B%22network%22:%22yahoo%22,%22display%22:%22popup%22,%22callback%22:%22_hellojs_2rb21r8x%22,%22state%22:%22%22%7D
Response:
http://local.knarly.com/hello.js/redirect.html#access_token=token&token_type=bearer&state=%7B+network+%3A+yahoo+%2C+display+%3A+popup+%2C+callback+%3A+_hellojs_2rb21r8x+%2C+state+%3A+%7D&xoauth_yahoo_guid=HROVGK6OHIIAXUNKG7KXOWUBVQ
So where does that leave us?
@mikelewis i'm waiting for Yahoo to respond to the bug report.
The state data could be saved in alternative ways, but i'm hoping yahoo will fix this soon.
Hey there, I finally had a chance to play around with Yahoo's OAuth2 API. To summarize for everyone, basically, there are 3 problems:
- the state parameter only accepts value with limited characters length.
- the state param returned in the callback redirect does not encode double quote characters properly (+ instead of %22)
- Cross origin AJAX response to an API endpoint still DOESN'T contain the necessary CORS header
Access-Control-Allow-Origin
.
Problem 1 & 2 are easily solvable by encoding the state differently and leaving out some of the longer params already known in app like client_id and callback URL. Problem 3, however, will need changes on Yahoo's end. One option to get around this is to proxy the AJAX request to Yahoo from your own domain. We are planning on doing this via Nginx. This really isn't ideal, but it'll work if you really want to use the OAuth2 API and not run the Node.js OAuth1 proxy.
I really hope Yahoo will get their isht together and fix all these problems soon...
@sahat can you shed light on this Auth issue with Yahoo OAuth2? (and keep your impressive 559 days streak on Github going :+1: )
No response from my bug report at https://hackerone.com/yahoo?.
If we all retweeet https://twitter.com/setData/status/580215026771881986 perhaps Yahoo Developer Network might get on this thread.
@MrSwitch I'm from the Yahoo Developer Network team. Thanks for the report! We're looking into this issue.
Thanks @saurabhsahni
@MrSwitch
Thats why in my solution, I had except for state>callback
parameter all passed to the processing page via PHP Session and reinserted into hello.js upon successful authentication
please help me.. i m getting "Could not decode state parameter" after yahoo login...
After getting inconsistent result with existing yahoo implementation. I got fed up and started implementing yahoo oauth 2.0 implementation by keeping the yahoo branch as base. Reached a point where i can access the data through chrome where i disabled the securities. Now the cross domain issue is haunting me. After doing so many things and tried so many methods it's a disappointment to see the developers didn't added cross domain access to api calls which was supposed to be called from different domain.
A simple change in hello.js
try {
var pState = p.state.replace(/"/g, '\"');
var a = JSON.parse(pState);
_this.extend(p, a);
}
catch (e) {
console.error('Could not decode state parameter');
}
Current yahoo.js
//
// Yahoo
//
// Register Yahoo developer
(function (hello) {
hello.init({
'yahoo': {
// Ensure that you define an oauth_proxy
oauth: {
version: 2,
auth: "https://api.login.yahoo.com/oauth2/request_auth",
grant: 'https://api.login.yahoo.com/oauth2/get_token'
},
// Login handler
login: function (p) {
// Change the default popup window to be atleast 560
// Yahoo does dynamically change it on the fly for the signin screen (only, what if your already signed in)
p.options.window_width = 560;
// Yahoo throws an parameter error if for whatever reason the state.scope contains a comma, so lets remove scope
try { delete p.qs.scope; delete p.qs.state.scope; } catch (e) { }
},
/*
// AUTO REFRESH FIX: Bug in Yahoo can't get this to work with node-oauth-shim
login : function(o){
// Is the user already logged in
var auth = hello('yahoo').getAuthResponse();
// Is this a refresh token?
if(o.options.display==='none'&&auth&&auth.access_token&&auth.refresh_token){
// Add the old token and the refresh token, including path to the query
// See http://developer.yahoo.com/oauth/guide/oauth-refreshaccesstoken.html
o.qs.access_token = auth.access_token;
o.qs.refresh_token = auth.refresh_token;
o.qs.token_url = 'https://api.login.yahoo.com/oauth/v2/get_token';
}
},
*/
base: "https://social.yahooapis.com/v1/",
get: {
"me": yql('select * from social.profile where guid=me'),
"me/friends": yql('SELECT * FROM social.contacts(0) WHERE guid=me'),
"me/following": yql('select * from social.contacts where guid=me')
},
wrap: {
me: formatUser,
// Can't get ID's
// It might be better to loop through the social.relationshipd table with has unique ID's of users.
"me/friends": formatFriends,
"me/following": formatFriends,
"default": paging
},
xhr: function (p) {
//alert(dump(p.options.access_token));
/*if( p.method !== 'get' && p.method !== 'delete' && !hello.utils.hasBinary(p.data) ){
// Does this have a data-uri to upload as a file?
if( typeof( p.data.file ) === 'string' ){
p.data.file = hello.utils.toBlob(p.data.file);
}else{
p.data = JSON.stringify(p.data);
p.headers = {
'Content-Type' : 'application/json'
};
}
}*/
alert("hi");
if (typeof (p.options.access_token) != 'undefined') {
p.headers = {
'Authorization': 'Bearer ' + p.options.access_token
};
}
return true;
}
}
});
function formatError(o) {
if (o && 'meta' in o && 'error_type' in o.meta) {
o.error = {
code: o.meta.error_type,
message: o.meta.error_message
};
}
}
function formatUser(o) {
formatError(o);
if (o.query && o.query.results && o.query.results.profile) {
o = o.query.results.profile;
o.id = o.guid;
o.last_name = o.familyName;
o.first_name = o.givenName || o.nickname;
var a = [];
if (o.first_name) {
a.push(o.first_name);
}
if (o.last_name) {
a.push(o.last_name);
}
o.name = a.join(' ');
o.email = (o.emails && o.emails[0]) ? o.emails[0].handle : null;
o.thumbnail = o.image ? o.image.imageUrl : null;
}
return o;
}
function formatFriends(o, headers, request) {
formatError(o);
paging(o, headers, request);
var contact;
var field;
if (o.query && o.query.results && o.query.results.contact) {
o.data = o.query.results.contact;
delete o.query;
if (!Array.isArray(o.data)) {
o.data = [o.data];
}
o.data.forEach(formatFriend);
}
return o;
}
function formatFriend(contact) {
contact.id = null;
// #362: Reports of responses returning a single item, rather than an Array of items.
// Format the contact.fields to be an array.
if (contact.fields && !(contact.fields instanceof Array)) {
contact.fields = [contact.fields];
}
(contact.fields || []).forEach(function (field) {
if (field.type === 'email') {
contact.email = field.value;
}
if (field.type === 'name') {
contact.first_name = field.value.givenName;
contact.last_name = field.value.familyName;
contact.name = field.value.givenName + ' ' + field.value.familyName;
}
if (field.type === 'yahooid') {
contact.id = field.value;
}
});
}
function paging(res, headers, request) {
// See: http://developer.yahoo.com/yql/guide/paging.html#local_limits
if (res.query && res.query.count && request.options) {
res.paging = {
next: '?start=' + (res.query.count + (+request.options.start || 1))
};
}
return res;
}
function yql(q) {
return 'https://query.yahooapis.com/v1/yql?q=' + (q + ' limit @{limit|100} offset @{start|0}').replace(/\s/g, '%20') + '&format=json';
}
})(hello);
@ratheeshkannan Yahoo seems to have gone quiet on this one.
@MrSwitch With no information from Yahoo team, I've asked the Backend team member to provide me a service which carries the parameters like auth_token, limit and offset which in turn he'll use it to hit yahoo server and give me back whatever that is coming from the yahoo server.
If you can, can you please help me in implementing it without modifying the core functionalities of hello.js. The flow is after getting the authorization token. I need to call my server (post) with necessary parameters and send the result to hello.js normal workflow.
How to interrupt the normal workflow of hello.js xhr call and overwrite it in yahoo.js so that i can insert a block of my code which should overwrite the normal workflow. I don't want to change anything in hello.js. But i can if necessary.
@ratheeshkannan what can you do with OAuth2 that you can't do with the current OAuth1 version? - since both have their drawbacks right now.
In OAuth1 version, I'm getting empty response many times (#364). Which is very much annoying but in Oauth2 I'm getting a 100% hit to yahoo servers and getting result consistently. So i think it's better to rely on Oauth2 rather than Oauth1.
I'm having no luck retrieving contacts with several solutions listed here. Is there any progress made on this?
Can I currently get contacts via yahoo?
@MrSwitch what is the status of this issue? Yahoo has started to have issues with OAuth 1 for their APIs and are advising to use 2...
@isaac2004 i've marked this as Good First Issue, hoping someone will investigate the latest Yahoo API and rework the Hello Yahoo module accordingly.