i am facing the following issue:
(I am using node-client-sessions module)
I send an ajax request, through the browser, to my API : /api/request1
In my api.js I handle that request, calculate some stuff and write some results into the session like this.
router.post('/request1', function(req, response, next) {
// some wield calculations
req.session.calcData = { // some content };
// some other calculations
console.log(req.session.calcData); // logs the correct object
response.send('success');
}
After receiving the success on client side I send another api call like this for example: /api/request2
router.post('/request2', function(req, response, next) {
// here i want to use some of the results from the previous calculations which i stored in the calcData session.
console.log(req.session.calcData); // this logs undefined
}
Shouldn't req.session.calcData be available in both functions?
Enviroment Info
Express Framework 4.x
app.js :
...
var app = express();
...
var session = require('client-sessions');
...
app.use(session({
cookieName: 'session',
secret: 'random_string_goes_here',
duration: 30 * 60 * 9999999,
activeDuration: 5 * 60 * 1000,
}));
...
app.use('/api', api);
According to example at client-session, you must use req.csession and req.csflush();
var express = require('express');
var path = require('path')
var cs = require('client-session');
var clientSession = cs('mysecretkey');
var app = express();
app.use(clientSession.connect());
app.get('/', function(req, res){
var count = req.csession['count'];
if(!count) count = 1;
else count++;
req.csession['count'] = count;
//sync to cookie session equal to res.csflush(),make sure to call it before response
req.csflush();
res.send(count.toString());
});
app.listen(8124);
console.log('Server running at http://127.0.0.1:8124/');
Related
I'm building a react app with an express server, and using OAuth 2 to securely access intuit APIs. The express server sends the authorization request, and then receives the authorization code at localhost:8000/callback where it's converted into an access token. The problem is that this access token is only accessible in the express app - so how do I send it to my react front end, so I can use it to securely make REST calls from react?
I've look at similar questions and there seems to be two solutions: use JWT, or store the access token on local storage. I'm just not sure where to start with either of these approaches, since I'm new to learning auth.
Anyway, my app structured so that my react app runs off a dev server at localhost:3000, and proxies request to my express server at localhost:8000.
Here's the express app:
'use strict';
require('dotenv').config();
/**
* Require the dependencies
* #type {*|createApplication}
*/
var express = require('express');
var app = express();
var path = require('path');
var OAuthClient = require('intuit-oauth');
var bodyParser = require('body-parser');
var ngrok = (process.env.NGROK_ENABLED==="true") ? require('ngrok'):null;
/**
* Configure View and Handlebars
*/
app.use(bodyParser.urlencoded({extended: true}));
app.use(express.static(path.join(__dirname, '/public')));
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(bodyParser.json())
var urlencodedParser = bodyParser.urlencoded({ extended: false });
/**
* App Variables
* #type {null}
*/
var oauth2_token_json = null,
redirectUri = '';
/**
* Instantiate new Client
* #type {OAuthClient}
*/
var oauthClient = null;
/**
* Home Route
*/
app.get('/', function(req, res) {
res.render('index');
});
/**
* Get the AuthorizeUri
*/
app.get('/authUri', urlencodedParser, function(req,res) {
oauthClient = new OAuthClient({
clientId: '*****',
clientSecret: '*****',
environment: 'sandbox',
redirectUri: 'http://localhost:8000/callback'
});
var authUri = oauthClient.authorizeUri({scope:[OAuthClient.scopes.Accounting],state:'intuit-test'});
res.send(authUri);
});
/**
* Handle the callback to extract the `Auth Code` and exchange them for `Bearer-Tokens`
*/
app.get('/callback', function(req, res) {
oauthClient.createToken(req.url)
.then(function(authResponse) {
oauth2_token_json = JSON.stringify(authResponse.getJson(), null,2);
})
.catch(function(e) {
console.error(e);
});
res.redirect('http://localhost:3000')
});
/**
* getCompanyInfo ()
*/
app.get('/getCompanyInfo', function(req,res){
var companyID = oauthClient.getToken().realmId;
var url = oauthClient.environment == 'sandbox' ? OAuthClient.environment.sandbox : OAuthClient.environment.production ;
oauthClient.makeApiCall({url: url + 'v3/company/' + companyID +'/companyinfo/' + companyID})
.then(function(authResponse){
console.log("The response for API call is :"+JSON.stringify(authResponse));
res.send(JSON.parse(authResponse.text()));
})
.catch(function(e) {
console.error(e);
});
});
app.get('/test', (req,res) => {
res.send('hello from server')
})
const server = app.listen(process.env.PORT || 8000, () => {
console.log(`💻 Server listening on port ${server.address().port}`);
})
The /authUri request is made by the browser via the proxy server, and its response is a redirect to the callback URL. Therefore, the callback URL must also be on the frontend server (i.e., http://localhost:3000/callback). The proxy server will then forward the /callback request to the express server, and the express server can put the access token in an (HTTP only) cookie that is sent back (via the proxy server) to the browser.
app.get('/callback', function(req, res) {
oauthClient.createToken(req.url)
.then(function(authResponse) {
res.cookie("session", authResponse.getJson().access_token,
{path: "/", httpOnly: true});
res.redirect('http://localhost:3000');
});
});
Verify that the proxy server preserves both sent and received cookies.
Then with every subsequent request, the browser will send the cookie (via the proxy server) back to the express server.
(Unlike the local storage, an HTTP only cookie is not accessible via JavaScript, which gives extra protection against cross-site scripting attacks.)
I know there's a few questions similar to this already, but they all have crucial differences which do not give me the solution.
I'm using client-session module. I've set everything up exactly like it's specified in the docs, but for some reason I can't set session values from async callback.
Here is my code
router.get('/auth', function(req, res, next) {
var params = req.query;
var act = params.act;
switch (act) {
case Constants.ACT_LOGIN_CHECK:
console.log('SESSION CHECK >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION CHECK >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
break;
case Constants.ACT_LOGIN_REQUEST:
var code = params.code;
var state = params.state;
login(code, state, function(result) {
if (result) {
if (result.accessToken) {
// console.log("WRITING SESSION BEFORE: ", JSON.stringify(req.session), req.session.isNew);
req.session.accessToken = result.accessToken;
req.session.userId = result.userId;
req.session.firstName = result.firstName;
req.session.lastName = result.lastName;
// req.session.photoURL = result.photoURL;
req.session.save();
console.log(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
res.send(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
}
} else {
res.end();
}
});
break;
case Constants.ACT_LOGOUT_REQUEST:
req.session = null;
res.send(`LOGGED OUT, SESSION >>> ${JSON.stringify(req.session)}`);
console.log('LOGGED OUT, SESSION >>> ', JSON.stringify(req.session));
break;
}
});
As you can see, I'm trying to set the session from callback function passed as third argument to my login() function. I can't say it doesn't work at all but it works, like, one time in a million. I'm not finilazing the response before the session is set, but anyway, when I check the session, this is what i get in the vast majority of cases:
SESSION CHECK >>> {} true
Even though this output
Is also shown all the time I set the session.
But if I simplify the code to this:
router.get('/auth', function(req, res, next) {
var params = req.query;
var act = params.act;
if (act == Constants.ACT_LOGIN_CHECK) {
console.log('SESSION CHECK >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION CHECK >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
} else if (act == Constants.ACT_LOGIN_REQUEST) {
req.session.accessToken = "595a751fa174ecbda109b14339b827941b58f7d0b10b495d0f85819e749e0b42c320dcda71520342cd020";
req.session.userId = 2195783;
req.session.firstName = "John";
req.session.lastName = "Doe";
req.session.save();
console.log('SETTING SESSION >>> ', JSON.stringify(req.session), req.session.isNew);
res.send(`SESSION SET >>> ${JSON.stringify(req.session)} ${req.session.isNew}`);
} else {
req.session = null;
console.log(`SESSION KILLED >>> ${JSON.stringify(req.session)}`);
res.send(`SESSION KILLED >>> ${JSON.stringify(req.session)}`);
}
}
It all works like a charm. The session is set / cleared / checked every time I want, with no errors or bugs.
I've been struggling with this issue for 3 days now :(. I've also tried different modules like client-sessions and several others. The same result. Seems like the problem is deeper.
So is it possible at all to save session from async callback? I can't set it until I get the necessary data from social network
p.s. Here is the actual initialization code from app.js
var Constants = require('./constants');
var express = require('express');
var path = require('path');
var app = express();
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieSession = require('./cookiesession');
var bodyParser = require('body-parser');
const user = require('./routes/user');
const auth = require('./routes/auth');
const posting = require('./routes/posting');
const messages = require('./messages');
const mongo = require('./mongo');
app.use(express.static(path.join(__dirname, 'public')));
app.set('trust proxy', true);
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieSession({
secret: Constants.COOKIE_SESSION_KEY_1
}));
app.use(function (req, res, next) {
req.sessionOptions.maxAge = Constants.COOKIE_SESSION_MAX_AGE;
next();
})
app.use('/api/', user);
app.use('/api/', posting);
app.use('/api/', auth);
module.exports = app;
MORE DETAILS NOW:
If the callback is executed in 1 second, the session will be set. But if it takes up more time, like 3+ seconds, it won't.
I've simplified my login() function like this:
function login(code, state, onComplete) {
setTimeout(function() {
onComplete({
accessToken: "sfsd646481351gsFFHgDrgrrg",
userId: "user",
firstName: "John",
lastName: "Doe"
})}, 1000
);
}
So in case I set 1000 ms to setTimeout() it works fine, but if I set it to 3000, it won't. Looks like express finalizes response before the callback is called
ANOTHER DETAIL:
I've just found out, that this only happens if I proxy the request from reactjs app. If I call API (express) server directly with postman, it won't matter how much time the response will wait, the cookie will be set with no problems
I found a reason why cookies were not set. It was because I made request to social network and it returned its headers before I could set mine
I'm trying to use the client-sessions middleware in nodejs / Express and I get the following error: Cannot set property 'mydata' of undefined.
I've also looked at this post, but could not find additional clues as to why I may be getting the error. node-js-client-sessions-not-creating-req-session
var bodyParser = require('body-parser');
const express = require('express');
const app = express();
const clientSessions = require("client-sessions");
var urlencodedParser = bodyParser.urlencoded({ extended: false });
var router = express.Router();
app.use(clientSessions({
cookieName: 'mydata', // cookie name dictates the key name added to the request object
secret: 'longsecretkeyorwhatever', // should be a large unguessable string
duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms
activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds
}));
/* Form POST handler */
router.post('/', urlencodedParser, function(req, res) {
if (!req.body) return res.sendStatus(400);
if(req.body.firstName){
req.session_state.mydata = req.body.mydata;
}
})
The documentation of client-sessions explains:
cookie name dictates the key name added to the request object
Since you're setting firstName as cookie name, the session object is available as req.firstName:
req.firstName.mydata = req.body.mydata
I am writing a custom middleware that generates a cryptographic signature of every request (it is very similiar to the authentication mechanism used by AWS API v4). In order for this signature to be correctly generated, I must fetch the entire raw body of the HTTP request.
I am also using BodyParser, which is registered after my custom middleware.
My custom middleware can be represented like this:
// libs/simplifiedSignatureCheckerMiddleware.js
module.exports = function (req, res, next){
// simple and fast hashing stuff
var payload = '';
req.on('data', function(chunk) { payload += chunk }, null);
req.on('end', function(){
// hmac stuff
console.log(payload);
var ok = true; // ...
if(ok)
next();
else
next("Bad")
});
}
This is how I use it on the server.
// simpleServer.js
// BASE SETUP
// =============================================================================
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var jsonStream = require('express-jsonstream');
var nconf = require('nconf');
var https = require('https');
var fs = require('fs');
// load configurations
nconf.argv().env();
nconf.file({file: 'config.json'});
app.use(require('./libs/simplifiedSignatureCheckerMiddleware'));
// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(jsonStream());
// ROUTES FOR API
// =============================================================================
var router = express.Router();
router.post('/api/', function (req, res) {
var param1 = req.body.param1 || "";
var param2 = req.body.param2 || "";
res.json({message: 'welcome', one: param1, two: param2 });
});
// REGISTER ROUTES
app.use(router);
// START THE SERVER
// =============================================================================
https.createServer({
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
}, app).listen(nconf.get('http:port'));
console.log("APIs listening on port " + nconf.get('http:port'));
As you can verify, the raw body is written successfully to the console by the middleware, BUT the request will never be processed by the registered route and the connection hangs forever.
Do you have any clue on how to solve this problem?
Thanks in advance.
Ok, since the only feasible way to solve this problem seems to be by modifying the original source code of bodyParser, I have forked it.
https://github.com/emanuelecasadio/body-parser-rawbody
This fork exposes the raw body of the request as a field named rawBody. As you can see, there is only ONE extra line of code.
You can install it by using npm install body-parser-rawbody.
EDIT
Another option is to use the bodyParser like this, as noted by dougwilson here: https://github.com/expressjs/body-parser/issues/83#issuecomment-80784100
app.use(bodyParser.json({verify:function(req,res,buf){req.rawBody=buf}}))
I haven't personally tried this option and I do not know if it works.
I have a simple express app that use session middleware together with passport-local middleware. Then I use share.js with browserchannel to stream data to server via share.listen(stream). All in align with documentation here.
My problem is that I cannot access session data (modified by passport-local and containing userID that was logged in) within stream. I need it to be able to restrict/grant access within client.on('message', function(data) {..}); based on some logic, but what of first importance is to check that the message came from logged in user. There, if I try to read ID it will be different from what potencialy is inside req.user._id. It seems that there share.js or browserchannel uses some different session, maybe?..
Here's the code:
var app = express();
var express = require('express');
...
// SETUP AND INIT
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: true,
limit: 1024 * 1024 * 10
}));
app.use(methodOverride());
app.use(session({
secret: global.CONFIG.session.secret,
maxAge: new Date(Date.now() + 1000 * 60 * 60 * 24 * 2),
store: new MongoStore(global.CONFIG.mongo),
resave: true,
saveUninitialized: true
}));
app.use(express.static(__dirname + '/build'));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());
// Create the sharejs server instance.
var backend = livedb.client(livedbMongo(global.CONFIG.mongo.url, false));
var share = sharejs.server.createClient({
db: backend
});
app.use(browserChannel(function(client) {
var stream = new Duplex({objectMode: true});
stream._write = function(chunk, encoding, callback) {
if (client.state !== 'closed') {
client.send(chunk);
}
callback();
};
stream._read = function() {
};
stream.headers = client.headers;
stream.remoteAddress = stream.address;
client.on('message', function(data) {
console.log(client.id) // <- I wish it was the same as in req.user._id..
stream.push(data);
});
stream.on('error', function(msg) {
client.stop();
});
client.on('close', function(reason) {
stream.emit('close');
stream.emit('end');
stream.end();
});
// Actually pass the stream to ShareJS
share.listen(stream);
}));
It seems to me, from looking at the code, that there might be a solution that won't require hacking the module:
var browserChannel = require('browserchannel').server;
var middleware = browserChannel(options, function(session, req) {
if (req.user) {
session.user = req.user;
}
});
app.use(middleware);
See here.
I have the same problem and I solved it by wrapping the browserchannel middleware constructor in a custom constructor:
function myMiddlewareConstructor () {
var request;
var bcMiddleware = browserChannel(function (client) {
//here you see the request
});
return function (req,res,next) {
request = req;
bcMiddleware(req,res,next);
}
}
app.use(myMiddlewareConstructor());
It avoids having to change the browserchannel code.
After several days of inspecting the code I have found a solution. If we look at this line in browserchannel/dist/server.js we can see that the session is being created using some information from initial request. We can modify this part of code by adding
session = createSession(req.connection.remoteAddress, query, req.headers);
// ----------- we add this ------------
session.user = {};
if( req.user )
session.user = req.user;
// ------------------------------------
This will add user session details from initial request to the session variable.