node-oauth Yahoo API oAuth2 issue - node.js

I'm building an app with node.js and express.js. I'm using the node-oauth module to connect to yahoo so I can make get requests to the api. I keep getting the error below
{ statusCode: 401,
data: '{"error":{"#lang":"en-US",
"#uri":"http://yahoo.com",
"description":"Not Authorized - Either YT cookies or a valid OAuth token must be passed for authorization","detail":"Not Authorized - Either YT cookies or a valid OAuth token must be passed for authorization"}}' }
After trying for a while to figure out my problem, I'm asking the community to check out my code and see what I am doing wrong. Code included below.
"use strict";
// declare libraries
var express = require('express');
var router = express.Router();
var OAuth = require('OAuth');
// set yahoo key and secret
var yahooKey = '*****************************************************';
var yahooSecret = '*********************************';
var oauth2 = new OAuth.OAuth2(
yahooKey,
yahooSecret,
'https://api.login.yahoo.com/',
'oauth2/request_auth',
'oauth2/get_token',
null
);
router.get('/', function(req, res, next) {
var access_token = oauth2.getOAuthAccessToken(
'',
{'grant_type':'authorization_code', 'redirect_uri':'http://www.domain.com'},
function (e, access_token, refresh_token, results) {
// console.log(e);
// done();
});
// console.log(oauth);
oauth2.get(
'https://social.yahooapis.com/v1/user/circuitjump/profile?format=json',
access_token,
function (error, data, response){
if (error) {
console.error(error);
}
// data = JSON.parse(data);
// console.log(JSON.stringify(data, 0, 2));
// console.log(response);
});
res.render('index', { title: 'Express' });
});
// export route
module.exports = router;
Any help is greatly appreciated. My brain is fried ...

You seem to be missing some steps. I would direct you first to this guide:
https://developer.yahoo.com/oauth2/guide/flows_authcode/
First, from your starting path at '/', you need to redirect (302) the user to an authorization page (Step 2 of yahoo's guide). The oauth lib has a helper for you to generate the correct URL:
var location = oauth2.getAuthorizeUrl({
client_id: yahooKey,
redirect_uri: 'https://yourservice.com/oauth2/yahoo/callback',
response_type: 'code'
});
res.redirect(location);
What you just did there is you redirected the user's browser to yahoo's authorization page, where the user gets a dialog asking if they want to allow your service XYZ access to do stuff on the user's behalf. Upon clicking "Allow", yahoo will redirect the browser to your callback url (Step 3 of yahoo's guide), providing you with an authorization code in the query params. In this example you have hooked up at /oauth2/yahoo/callback
You can set that up like so (Step 4 of yahoo's guide):
router.get('/oauth2/yahoo/callback', function(req, res) {
// Aha now I have an authorization code!
var code = req.query.code;
oauth2.getOAuthAccessToken(
code,
{
'grant_type': 'authorization_code',
'redirect_uri': 'oob'
},
function(e, access_token, refresh_token, results) {
console.log('Now I have a token', access_token, 'that I can use to call Yahoo APIs!');
res.end();
});
});
I hope all of that makes some sense. I'll leave it as an exercise for you to figure out the refresh token (step 5). If you make it this far, that part should be easy :)
Edit
It looks like Yahoo also requires you to send your key and secret in a Authorization Basic header. You can generate this header and tell the oauth2 module to include it like so:
var encoded = new Buffer(yahooKey+":"+yahooSecret).toString('base64')
var authHeader = "Basic " + encoded;
var oauth2 = new OAuth.OAuth2(
yahooKey,
yahooSecret,
'https://api.login.yahoo.com/',
'oauth2/request_auth',
'oauth2/get_token',
{ Authorization: "Basic " + authHeader}
);

Related

Oauth2 with Google docs API Node.js (trying to programmatically write a new google doc)

I have a typical web app with a client and a node.js server. When a user selects an option on the client, I want to export (create) a google doc in their drive.
I am half way there following this tutorial https://developers.google.com/identity/protocols/oauth2/web-server
With my current set up, after the user authenticates, the authentication token is sent to a web hook (server side), but I don't have any of the data for creating the google doc there.
If I did, I could create the doc from there. Otherwise, I need to send the token itself back to the client so I can create the doc with the necessary payload from there.
In that case, I don't know how to signal to the client that the user has been authenticated. Should I use a web socket?
Something tells me that my general set up might not be correct, and that I should be doing it a different way in my use case.
This is my client side code that brings the user to the google auth page after getting the auth url (not sure if this really needs to be done server side, but since I have some user credentials I thought it might be better).
export async function exportToGoogleDoc() {
const response = await POST(`${API_URL}export/gdoc`, {
'hello': 'world'
});
if (response.status == 200){
window.location.href = response.authUrl;
}
}
And then the endpoint (just returns the autheticationUrl)
api.post('/export/gdoc', express.raw({ type: 'application/json' }), async (req, res, next) => {
try {
const scopes = [
'https://www.googleapis.com/auth/drive'
];
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
const authorizationUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: true
});
res.json({ 'status': 200, authUrl : authorizationUrl } );
} catch (err){
next(err);
}
});
But then after the user agrees and authenticates with their google account, the auth token is sent to this web hook. At the bottom I am able to write an empty google doc to the authenticated google drive, but I don't have the data I need to create the real doc.
api.get('/auth/google', express.raw({ type: 'application/json' }), async (req, res, next) => {
const q = url.parse(req.url, true).query;
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
if (q.error) {
console.log('Error:' + q.error);
} else {
const { tokens } = await oauth2Client.getToken(q.code.toString());
oauth2Client.setCredentials(tokens);
const drive = google.drive('v3');
const requestBody = {
'name': 'Dabble',
'mimeType': 'application/vnd.google-apps.document',
};
drive.files.create({
requestBody: requestBody,
fields: 'id',
auth: oauth2Client
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file);
}
});
}
Somehow I either need to get the data for the google doc inside this web hook, or to listen for this web hook from the client.
OR I need an entirely different set up. I realize I also should be probably storing this token somewhere (local storage on client side?) and only making this call if they do not have a token.
Can anyone help me modify my set up? Thanks!

OAuth2 & Node.js - No redirect after Google confirmation

I'm using Node.js to authenticate my web application with Google+. I've followed the official instructions here. My code looks like this:
var google = require('googleapis');
// OAuth
var OAuth2 = google.auth.OAuth2;
var plus = google.plus('v1');
var oauth2Client = new OAuth2(
'MY_CLIENT_ID', // Client id
'MY_CLIENT_SECRET', // Client secret
'http://localhost:8080/oauth' // Redirect url
);
function getOAuthUrl(){
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/plus.me'
});
return url;
}
// OAuth authorization
app.use('/oauth', function(req, res){
var session = req.session;
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if (!err) {
oauth2Client.setCredentials(tokens);
session['tokens'] = tokens;
res.redirect(__dirname + '/public/html/redirect.html?r=1'); // Success!
}else{
res.redirect(__dirname + '/public/html/redirect.html?r=0'); // Fail!
}
});
});
The login page is called index.html at the root of my folder. The login page makes an ajax request to /oauth/url which responds with the OAuth2 url that the user must click.
JS on index.html:
/* OAuth */
$.ajax({
url: '/oauth/url',
dataType: 'text',
cache: false,
success: function (e) {
$('#login').attr('href', e);
}
});
Node.js response:
// Get OAuth URL
app.use('/oauth/url', function(req, res){
var url = getOAuthUrl();
res.end(url);
});
I can click the link to take me to the authentication page as normal. But when I select the account to authenticate, the page freezes and doesn't get redirected to localhost:8080/oauth like it's supposed to.
UPDATE:
Looking at the networking tab on the console I noticed that the GET request to the callback is being cancelled. The code is recieved by Node.js and so is the request.
Solved:
The issue was with the static directory not allowing Node.js to redirect the page. Fixed by changing the redirect address to /html/redirect.html. Thank you #James.

Auth0 "service not found" error

I'm attempting to use Auth0 to issue JWT tokens for accessing my API (so that Auth0 handles all the OAuth and security concerns, etc., and my API just needs to check the token). When I try to test the Authorization Code flow for clients to receive an access token (using Node + Express), the following happens:
The authorization code request works fine, and the client is redirected back to my redirect_uri with the code appended to the query. All good.
The token request then always fails. If I include the audience parameter, the request returns an access_denied error with the following details: Service not found: {the audience parameter}, regardless of what value I set for the audience parameter.
If I don't include the audience parameter, I get a server_error with the message Service not found: https://oauth.auth0.com/userinfo.
I've checked every Auth0 setting and read every documentation page thoroughly, and so far nothing has worked. I've also tested the Authorization Code flow in Auth0's API debugger, and it worked fine. My test follows exactly the same parameters, and yet still receives an error requesting the token. I'm testing on localhost. The client credentials and implicit flows are working fine.
Here is a test endpoint I created which retrieves the authorization code from Auth0:
const qs = require('querystring');
const getCode = (req, res) => {
const params = {
audience, // the value of the API Audience setting for the client
client_id, // the client ID
redirect_uri, // the redirect_uri, which is also listed in the Allowed Callback URLs field
response_type: `code`,
scope: `offline_access open` // ask to return ID token and refresh token,
state: `12345`,
};
const authDomain = `mydomain.auth0.com/oauth`;
res.redirect(`${authDomain}/oauth/authorize?${qs.stringify(params)}`);
};
The redirect_uri then redirects to the following endpoint, where I make the request for the access token:
const https = require('https');
const callback = (req, res) => {
const body = {
client_id,
client_secret,
code: req.query.code,
grant_type: `authorization_code`,
redirect_uri, // same value as provided during the code request
};
const opts = {
headers: { 'Content-Type': `application/json` },
hostname: `mydomain.auth0.com`,
method: `POST`,
path: `/oauth/token`,
};
const request = https.request(opts, response => {
let data = ``;
response.on(`data`, chunk => { data += chunk; });
response.on(`error`, res.send(err.message));
response.on(`end`, () => res.json(JSON.parse(data))); // this executes, but displays the error returned from Auth0
});
request.on(`error`, err => res.send(err.message));
request.end(JSON.stringify(body), `utf8`);
};
Any suggestions as to what I might be doing wrong?
The issue was that I was calling the incorrect URL at Auth0. I mistakenly thought that both the authorization and token endpoints began with /oauth, when in fact the authorization endpoint is just /authorize, while the token endpoint is /oauth/authorize. Correcting the URLs in my code fixed the problem.
My solution was the identifier of the api was not found. If it is not exact it won't find it. I had an extra backslash on my 'audience' where the identifier didnt have one. pretty easy mistake but the error is not very clear in Auth0.
In my case, I was using auth0 react hooks. So the example code looked like this:
const getUserMetadata = async () => {
const domain = process.env.REACT_APP_AUTH0_DOMAIN
try {
const accessToken = await getAccessTokenSilently({
audience: `https://${domain}/api/v2/`,
scope: 'read:current_user',
})
console.log('accessToken', accessToken)
localStorage.setItem('access_token', accessToken)
setUserAuthenticated(true)
} catch (e) {
console.log('error in getting access token', e.message)
}
}
My solution to this was using by default Auth0 Audience value in audience field
const getUserMetadata = async () => {
const auth0audience = process.env.REACT_APP_AUTH0_AUDIENCE
try {
const accessToken = await getAccessTokenSilently({
audience: auth0audience,
scope: 'read:current_user',
})
console.log('accessToken', accessToken)
localStorage.setItem('access_token', accessToken)
setUserAuthenticated(true)
} catch (e) {
console.log('error in getting access token', e.message)
}
}
Because its stated in auth0 docs of configuring custom domains that, you need to use by default API audience
Source - https://auth0.com/docs/brand-and-customize/custom-domains/configure-features-to-use-custom-domains

Node.js OAuth2: Get Google+ activities

From today I'm not able to retrieve my Google+ activities by placing a GET request to this url:
https://www.googleapis.com/plus/v1/people/me/activities/public?key={API_KEY}
I'm getting a 401 error. So I tried to sign the request using the node.js oauth2 library, including a pre-generated user token, but I'm getting the same error. The js code for my test script is:
var OAuth2 = require('oauth').OAuth2,
GOOGLEPLUS_APP_ID = {APP_ID},
GOOGLEPLUS_APP_SECRET = {APP_SECRET},
accessToken = {USER_TOKEN};
var _oauth2 = new OAuth2(GOOGLEPLUS_APP_ID, GOOGLEPLUS_APP_SECRET, '',
'https://accounts.google.com/o/oauth2/auth',
'https://accounts.google.com/o/oauth2/token');
_oauth2.get('https://www.googleapis.com/plus/v1/people/me/activities/public?key={API_KEY}',
accessToken, function (err, body, res) {
if (err) {
console.log('error:');
console.log(err);
}
else console.log(body);
});
I placed the same request to get the basic user info (url: https://www.googleapis.com/oauth2/v1/userinfo) and works OK.
Can you please assist me with this?
Thank you.
If you have the access token already you should be able to use {?|&}access_token=X to get the result, example below.
var accessToken = 'XYZ';
require('http').get('https://www.googleapis.com/plus/v1/people/me/activities/public?access_token=' + accessToken, function(result){
//Use result here!
});
More information can be found within the Google+ API (Common Parameters) section

dropbox api usage in nodejs "Bad oauth_signature for oauth_signature_method"

I have been trying to connect to dropbox server and use the api, but I'm failing at the first step itself. When i'm requesting for the request token, I'm getting Bad oauth_signature error in nodejs.
The code that i'm using to connect to api is as follows.(I'm using https://github.com/sintaxi/node-dbox/blob/master/README.md library/sdk for nodejs)
/*
* dropbox handlers controller.
*/
var dbox = require('dbox')
,querystring = require("querystring")
var client = dbox.createClient({
app_key : 'my-key', // required
app_secret : 'my-secret', // required
root : 'sandbox' // optional (defaults to sandbox)
})
exports.index = function(req, res){
client.request_token(function(status, reply){
console.log(status)
console.log(reply)
// {
// oauth_token : "h89r0sdfdsfwiko", // required
// oauth_token_secret : "8hielfflk7100mv", // required
// }
})
the result i'm getting in my console is as follows
c:\tmp\dropbox>node app.js
Express server listening on port 3000 in development mode
oauth_consumer_key=[my key]&oauth_signature=faawn09ehmfe25i%2526&oauth_ti
mestamp=1324643574&oauth_nonce=132464357437334176&oauth_signature_method=PLAINTE
XT&oauth_version=1.0
403
{ '{"error": "Bad oauth_signature for oauth_signature_method \'PLAINTEXT\'"}': u
ndefined }
Any help on this is greatly appreciated.
Thanks in advance
This is the author of node-dbox. This issue has been resolved as of version 0.2.2.
Sorry for the trouble.
I took the approach of using the passport module along with its companion passport-dropbox module to handle the routes required for the authentication handshake with Dropbox. Once you receive the token and token secret passed in the Dropbox callback, store them in session state or wherever. Then you can then pass them to node-dbox in subsequent Dropbox API calls. The author of passport has a nice example on GitHub here: https://github.com/jaredhanson/passport-dropbox/tree/master/examples/login
passport.use(new DropboxStrategy({
consumerKey: DROPBOX_APP_KEY,
consumerSecret: DROPBOX_APP_SECRET,
callbackURL: APP_URL + '/auth/dropbox/callback'
},
function(token, tokenSecret, profile, done) {
var user = {
provider: 'dropbox',
displayName: profile.displayName,
email: profile.emails[0].value,
// I'm choosing to store the token and tokenSecret on the user.
// The keys must be as shown below to be compatible with node-dbox
dboxToken: {'oauth_token': token, 'oauth_token_secret': tokenSecret}
};
return done(null, user);
}));
app.get('/files', function(req, res) {
var dboxClient = dboxApp.client(req.user.dboxToken);
dboxClient.metadata('/', {}, function(status, reply) {
res.render('files', {
pathMetaData: reply,
user: req.user
});
});
});
To fix that issue you just need to apply what is mentioned here :
https://github.com/sintaxi/node-dbox/issues/3
On line 28 of Oauth.js signature is being encoded twice.
var getSignature = function(tokenSecret){
return encode(consumerSecret) + "&" + encode(tokenSecret)
}
var signature = encode(getSignature(secret))
Changing it to the following solves the problem of not being able to receive an oauth token.
var signature = getSignature(secret)
Thx

Resources