How to keep node-dbox token between page refreshes in NodeJS/Express - node.js

Im trying to put together a little application using NodeJS, node-dbox and Express.
When requesting for DropBox authorization - it's a 3 step process, first need to get request_token, then user authorizes them visiting dropbox page, and only then request for access_token, based on request_token and the fact that user has authorized request.
However, by the time I served the page for step 1 and 2 (getting request_token, and providing user with url) - request_token instance is gone!, so in step 3 I can't request for an access_token, because it requires request_token being passed
I'm trying to save request_token in a cookie, but given that contains sensitive data, sending it to the client may not be such a good idea. Any ideas?
Simplified code is below:
(function() {
var dbox = require('dbox'),
config = require('easy-config'),
express = require('express'),
dboxApp = dbox.app(config.dropbox_credentials),
app = express();
app.use(express.cookieParser());
app.get('/', function(req, res) {
dboxApp.requesttoken(function(status, request_token) {
res.cookie('request_token', JSON.stringify(request_token));
res.send("<a href='" + request_token.authorize_url + "' targe='_new'>authorize via dropbox</a><br/>" + "<a href='/next'>next</a>");
});
});
app.get('/next', function(req, res) {
var request_token = JSON.parse(req.cookies.request_token);
if(request_token) {
dboxApp.accesstoken(request_token, function(status, access_token) {
var client = dboxApp.client(access_token);
client.account(function(status, reply){
res.send(reply);
});
});
} else {
res.send('sorry :(');
}
});
app.listen(3000);
})();
bonus question: client is created with access_token, so either instance of client or access_token need to be maintained across page refreshes as well, whats the best approach?

I managed to get it working by doing the following:
According to the Dropbox Developer reference you can provide a callback url by specifying it along with the request as stated here:
https://www.dropbox.com/developers/blog/20
https://www.dropbox.com/1/oauth/authorize?oauth_token=<request-token>&oauth_callback=<callback-url>
By storing the request token in the session and redirecting to the callback url you can then access the request token and be on your way.
A couple of Express route handlers, passed a member id as a parameter, to request and then handle the response might look like this:
linkAccount : function(req, res){
var memberId = req.params.memberId,
appKey = 'MYAPPKEY',
appSecret = 'MYAPPSECRET',
dbox = require('dbox'),
dboxApp = dbox.app({ "app_key": appKey, "app_secret": appSecret });
req.session.dboxStore = {};
req.session.dboxStore.dboxApp = dboxApp;
dboxApp.requesttoken(function(status, request_token){
req.session.dboxStore.request_token = request_token;
console.log("request_token = ", request_token);
res.redirect('https://www.dropbox.com/1/oauth/authorize?oauth_token='+request_token.oauth_token+
'&oauth_callback=http://myhost.local/linksuccess/dropbox/'+memberId);
res.end;
});
},
linkSuccess : function(req, res){
var memberId = req.params.memberId;
var appKey = 'MYAPPKEY';
var appSecret = 'MYAPPSECRET';
var dbox = require('dbox');
var dboxApp = dbox.app({ "app_key": appKey, "app_secret": appSecret });
var request_token = req.session.dboxStore.request_token;
dboxApp.accesstoken(request_token, function(status, access_token){
console.log('access_token = ', access_token);
Member.setAuthToken(memberId, 'dropbox', access_token, function(err, member){
res.render('index', { title:'SUCCESSFUL DROPBOX AUTH' });
res.end;
});
});
}

Related

Dynamics Business Central Azure AD ADAL Unauthorized

I developed a bare bones express app to test auth with Dynamics Business Central and ADAL in NodeJS. I'm getting the following 401 error. The authentication works as expected in Postman and I'm able to call the Dynamics REST endpoint in that context. In the JavaScript below I am using the same AAD tenant, client id, and client secret in Postman, but I'm not able to authenticate.
Compared the auth tokens given over Postman and in NodeJs using https://jwt.io/ and the only difference is the header values and the uti in the payload.
When I hit me getcompanies route I get the following error. I've listed my node package versions at the bottom of the post.
Error { error: { code: '401', message: 'Unauthorized' } }
Source code
var AuthenticationContext = require('adal-node').AuthenticationContext;
var crypto = require('crypto');
var express = require('express');
var request = require('request');
require('dotenv').config()
var clientId = process.env.CLIENT_ID;
var clientSecret = process.env.CLIENT_SECRET;
var authorityHostUrl = 'https://login.windows.net';
var azureAdTenant = 'grdegr.onmicrosoft.com';
var dynBusinessCentralCommonEndpoint = 'https://api.businesscentral.dynamics.com/v1.0/' + azureAdTenant + '/api/beta';
var bcRedirectUri = 'http://localhost:1337/getbctoken';
var dynBusinessCentralAuthUrl = authorityHostUrl + '/' +
azureAdTenant +
'/oauth2/authorize?response_type=code&client_id=' +
clientId +
'&redirect_uri=' +
bcRedirectUri +
'&state=<state>&resource=' +
'https://api.businesscentral.dynamics.com';
var app = express();
var port = 1337;
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
app.get('/bcauth', function(req, res) {
crypto.randomBytes(48, function(ex, buf) {
var bcToken = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
res.cookie('bcauthstate', bcToken);
var dynBusinessCentralAuthUrlauthorizationUrl = dynBusinessCentralAuthUrl.replace('<state>', bcToken);
console.log('redirecting to auth url: ' + dynBusinessCentralAuthUrlauthorizationUrl);
res.redirect(dynBusinessCentralAuthUrlauthorizationUrl);
});
});
var bcAccessToken = '';
app.get('/getbctoken', function(req, res) {
var authorityUrl = authorityHostUrl + '/' + azureAdTenant;
var authenticationContext = new AuthenticationContext(authorityUrl);
console.log('getting bc auth context');
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
bcRedirectUri,
'https://api.businesscentral.dynamics.com/',
clientId,
clientSecret,
function(err, response) {
var message = '';
if (err) {
message = 'error: ' + err.message + '\n';
return res.send(message)
}
bcAccessToken = response.accessToken;
console.log('bc token\n' + bcAccessToken);
res.send('bc access token updated');
}
);
});
app.get('/getcompanies', (req, res) => {
var body = '';
var options = {
url: 'https://api.businesscentral.dynamics.com/v1.0/grdegr.onmicrosoft.com/api/beta/companies',
method: 'GET',
headers: {
Authorization: 'Bearer ' + bcAccessToken
},
json: JSON.stringify(body)
};
request(options, (err, response, body) => {
res.send(response || err);
if (response) {
console.log(body);
}
else {
console.log('response is null');
}
});
});
Node Packages
"devDependencies": {
"adal-node": "^0.1.28",
"request": "^2.87.0",
"webpack": "^4.12.0",
"webpack-cli": "^3.0.8"
},
"dependencies": {
"dotenv": "^6.1.0"
}
Some services are very strict when checking the aud (audience) value of an access token. Dynamics 365 Business Central expects the access token audience to be exactly https://api.businesscentral.dynamics.com. In your code, you are asking for, and getting an access token for https://api.businesscentral.dynamics.com/. That trailing slash at the end is what is making Dynamics 365 reject your access token invalid.
Change the token request to:
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
bcRedirectUri,
'https://api.businesscentral.dynamics.com', // <-- No trailing slash!
clientId,
clientSecret,
// ...
...and it should work.
However, there are two important things to note in your sample:
The pattern you are following is a bit strange, though it may be because you're in the early stages of development, or because it was just a minimal repro example for this question. You should not store an access token that way, because the next person who calls /getcompanies will be able to do so calling on behalf of the user who originally signed in, instead of signing in themselves. If you are looking to have users sign in with Azure AD and as part of that, call Dynamics 365 on behalf of the signed-in user, I suggest looking at passport-azure-ad.
Especially if you plan to have a system-wide account or access token, be very careful returning the original response to the end user. This is true even when developing, since it's very easy to overlook something like that when moving to production, and exposing what could be a very privileged access token to an unauthorized user.

OT_AUTHENTICATION_ERROR: Invalid token format (1004)

I am getting a 1004 error when trying to run the opentok api on the server, it is running fine on the localhost but when i ran the same code on the server, it gave me this opentok_authentication_error.
I have checked the apikey and serverkey multiple times and they both are correct, and the session id is also the same.
I can publish and subscribe sessions on the localhost, everything is working fine there but when i uploaded my website on the server and ran it their it gave me this error.
I've also checked the token value generated as well and it is the same which is being generated on the opentok website.
For frontend, I am using angular.js, I dont know what I am doing wrong and when it is working fine on localhost why is giving this error when running on a server.
server.js
Opentok = require('opentok');
var OTConfig = require('./config/otconfig');
var opentok = new Opentok(OTConfig.apiKey, OTConfig.secretKey);
var OT = require('./routes/opentok')(opentok, app);
routes/opentk.js
var OT_config = require('../config/dbconfig');
var express = require('express');
var router = express.Router();
module.exports = function(opentok, app) {
app.post('/opentok/join', function(req, res) {
var token,
sessionId = req.body.ot_session,
tokenOptions = {};
tokenOptions.role = "publisher";
tokenOptions.data = "username=" + req.body.name;
tokenOptions.data += "courseId=" + req.body.course;
tokenOptions.data += "role=" + req.body.role;
// Generate a token.
token = opentok.generateToken(sessionId, tokenOptions);
res.send(token);
});
}
controller.js
var opent = opentokService.generateToken(sessionId);
opent.then(function(data) {
$rootScope.token = data.data;
});
opentok service
return {
generateToken: function(id) {
let info = {
name: $rootScope.username,
id: id,
role: $rootScope.role,
ot_session: $rootScope.sessionId
}
return $http({
method: 'post',
url: '/opentok/join',
data: info
});
}
}
First, check your opentok account subscription is paused or not if it is paused then do the need full to activate your account.

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.

Sign in to Nodejs web app, using Azure AD

I'm trying to figure out how you can authenticate users using Azure AD. In order to experiment, I tried the example from Microsoft, found at https://github.com/Azure-Samples/active-directory-node-webapp-openidconnect.
I've set up an Active Directory in Azure, and added a new application called test with add id uri: http://testmagnhalv.
Now, when I run the server, following the instructions in the readme, I get redirected to login.microsoftonline.com and promted to log in. But when I provide username/pw, I get redirected back to the login page again.
I suspect the problem is that I don't set the variables in the config.json correctly, but I'm having a hard time finding documentation for what values need to be set.
Anyone got any experience with this example?
At first you must to add your app to active directory then use ADAL (Active Directory Authentication Library) for nodeJS
npm install adal-node
prepare your app for authentication referencing azure AD App registration values.
var AuthenticationContext = require('adal-node').AuthenticationContext;
var clientId = 'yourClientIdHere';
var clientSecret = 'yourAADIssuedClientSecretHere'
var redirectUri = 'yourRedirectUriHere';
var authorityHostUrl = 'https://login.windows.net';
var tenant = 'myTenant';
var authorityUrl = authorityHostUrl + '/' + tenant;
var redirectUri = 'http://localhost:3000/getAToken';
var resource = '00000002-0000-0000-c000-000000000000';
var templateAuthzUrl = 'https://login.windows.net/' +
tenant +
'/oauth2/authorize?response_type=code&client_id=' +
clientId +
'&redirect_uri=' +
redirectUri + '
&state=<state>&resource=' +
resource;
Now you need to get authorized with the token.
function createAuthorizationUrl(state) {
return templateAuthzUrl.replace('<state>', state);
}
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
// some resource they own.
app.get('/auth', function(req, res) {
crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
res.cookie('authstate', token);
var authorizationUrl = createAuthorizationUrl(token);
res.redirect(authorizationUrl);
});
});
And finally handle the auth redirection
// After consent is granted AAD redirects here. The ADAL library is invoked via the
// AuthenticationContext and retrieves an access token that can be used to access the
// user owned resource.
app.get('/getAToken', function(req, res) {
if (req.cookies.authstate !== req.query.state) {
res.send('error: state does not match');
}
var authenticationContext = new AuthenticationContext(authorityUrl);
authenticationContext.acquireTokenWithAuthorizationCode(
req.query.code,
redirectUri,
resource,
clientId,
clientSecret,
function(err, response) {
var errorMessage = '';
if (err) {
errorMessage = 'error: ' + err.message + '\n';
}
errorMessage += 'response: ' + JSON.stringify(response);
res.send(errorMessage);
}
);
});
You can find the full example, and more here in ADAL for nodeJS repository:
Windows Azure Active Directory Authentication Library (ADAL) for Node.js
This is a simple but full demo taken from GitHub ADAL repository
website-sample.js
'use strict';
var express = require('express');
var logger = require('connect-logger');
var cookieParser = require('cookie-parser');
var session = require('cookie-session');
var fs = require('fs');
var crypto = require('crypto');
var AuthenticationContext = require('adal-node').AuthenticationContext;
var app = express();
app.use(logger());
app.use(cookieParser('a deep secret'));
app.use(session({secret: '1234567890QWERTY'}));
app.get('/', function(req, res) {
res.redirect('login');
});
/*
* You can override the default account information by providing a JSON file
* with the same parameters as the sampleParameters variable below. Either
* through a command line argument, 'node sample.js parameters.json', or
* specifying in an environment variable.
* {
* "tenant" : "rrandallaad1.onmicrosoft.com",
* "authorityHostUrl" : "https://login.windows.net",
* "clientId" : "624ac9bd-4c1c-4686-aec8-e56a8991cfb3",
* "clientSecret" : "verySecret="
* }
*/
var parametersFile = process.argv[2] || process.env['ADAL_SAMPLE_PARAMETERS_FILE'];
var sampleParameters;
if (parametersFile) {
var jsonFile = fs.readFileSync(parametersFile);
if (jsonFile) {
sampleParameters = JSON.parse(jsonFile);
} else {
console.log('File not found, falling back to defaults: ' + parametersFile);
}
}
if (!parametersFile) {
sampleParameters = {
tenant : 'rrandallaad1.onmicrosoft.com',
authorityHostUrl : 'https://login.windows.net',
clientId : '624ac9bd-4c1c-4686-aec8-b56a8991cfb3',
username : 'frizzo#naturalcauses.com',
password : ''
};
}
var authorityUrl = sampleParameters.authorityHostUrl + '/' + sampleParameters.tenant;
var redirectUri = 'http://localhost:3000/getAToken';
var resource = '00000002-0000-0000-c000-000000000000';
var templateAuthzUrl = 'https://login.windows.net/' + sampleParameters.tenant + '/oauth2/authorize?response_type=code&client_id=<client_id>&redirect_uri=<redirect_uri>&state=<state>&resource=<resource>';
app.get('/', function(req, res) {
res.redirect('/login');
});
app.get('/login', function(req, res) {
console.log(req.cookies);
res.cookie('acookie', 'this is a cookie');
res.send('\
<head>\
<title>FooBar</title>\
</head>\
<body>\
Login\
</body>\
');
});
function createAuthorizationUrl(state) {
var authorizationUrl = templateAuthzUrl.replace('<client_id>', sampleParameters.clientId);
authorizationUrl = authorizationUrl.replace('<redirect_uri>',redirectUri);
authorizationUrl = authorizationUrl.replace('<state>', state);
authorizationUrl = authorizationUrl.replace('<resource>', resource);
return authorizationUrl;
}
// Clients get redirected here in order to create an OAuth authorize url and redirect them to AAD.
// There they will authenticate and give their consent to allow this app access to
// some resource they own.
app.get('/auth', function(req, res) {
crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('base64').replace(/\//g,'_').replace(/\+/g,'-');
res.cookie('authstate', token);
var authorizationUrl = createAuthorizationUrl(token);
res.redirect(authorizationUrl);
});
});
// After consent is granted AAD redirects here. The ADAL library is invoked via the
// AuthenticationContext and retrieves an access token that can be used to access the
// user owned resource.
app.get('/getAToken', function(req, res) {
if (req.cookies.authstate !== req.query.state) {
res.send('error: state does not match');
}
var authenticationContext = new AuthenticationContext(authorityUrl);
authenticationContext.acquireTokenWithAuthorizationCode(req.query.code, redirectUri, resource, sampleParameters.clientId, sampleParameters.clientSecret, function(err, response) {
var message = '';
if (err) {
message = 'error: ' + err.message + '\n';
}
message += 'response: ' + JSON.stringify(response);
if (err) {
res.send(message);
return;
}
// Later, if the access token is expired it can be refreshed.
authenticationContext.acquireTokenWithRefreshToken(response.refreshToken, sampleParameters.clientId, sampleParameters.clientSecret, resource, function(refreshErr, refreshResponse) {
if (refreshErr) {
message += 'refreshError: ' + refreshErr.message + '\n';
}
message += 'refreshResponse: ' + JSON.stringify(refreshResponse);
res.send(message);
});
});
});
app.listen(3000);
console.log('listening on 3000');
https://github.com/AzureAD/azure-activedirectory-library-for-nodejs/blob/master/sample/website-sample.js
As I known, I suggest you can follow the two documents below as references to get start.
Web App Sign In & Sign Out with Azure AD https://azure.microsoft.com/en-us/documentation/articles/active-directory-devquickstarts-openidconnect-nodejs/
Integrating Azure AD into a NodeJS web application https://azure.microsoft.com/en-us/documentation/samples/active-directory-node-webapp-openidconnect/
For developing easier, you can try to use the node package passport-azure-ad(https://github.com/AzureAD/passport-azure-ad) that is the one strategy of passport (http://passportjs.org/) for NodeJS to implement your needs.
I had similar problem and able to resolve it. After googling i made two changes in config.js.
issuer value set to false.
responseMode value changed from query to form_post.
config.js :
exports.creds = {
issuer : false,
realm : "<TENANT>",
returnURL: 'http://localhost:3000/auth/openid/return',
identityMetadata: 'https://login.microsoftonline.com/common/.well-known/openid-configuration', // For using Microsoft you should never need to change this.
clientID: '<CLIENT_ID>',
clientSecret: '<CLIENT_SECRET>', // if you are doing code or id_token code
skipUserProfile: true, // for AzureAD should be set to true.
responseType: 'id_token code', // for login only flows use id_token. For accessing resources use `id_token code`
responseMode: 'form_post', // For login only flows we should have token passed back to us in a POST
};

node-oauth Yahoo API oAuth2 issue

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}
);

Resources