NodeJS is a fantastic tool and blazing fast.
I'm wondering if HTTPClient supports cookies and if can be used in order to simulate very basic browser behaviour!
Help would be very much appreciated! =)
EDIT:
Found this: node-httpclient (seems useful!) not working!
Short answer: no. And it's not so great.
I implemented this as part of npm so that I could download tarballs from github. Here's the code that does that: https://github.com/isaacs/npm/blob/master/lib/utils/fetch.js#L96-100
var cookie = get(response.headers, "Set-Cookie")
if (cookie) {
cookie = (cookie + "").split(";").shift()
set(opts.headers, "Cookie", cookie)
}
The file's got a lot of npm-specific stuff (log, set, etc.) but it should show you the general idea. Basically, I'm collecting the cookies so that I can send them back on the next request when I get redirected.
I've talked with Mikeal Rogers about adding this kind of functionality to his "request" util, complete with supporting a filesystem-backed cookiejar, but it's really pretty tricky. You have to keep track of which domains to send the cookies to, and so on.
This will likely never be included in node directly, for that reason. But watch for developments in userspace.
EDIT: This is now supported by default in Request.
If you are looking to do cookies client side you can use https://github.com/mikeal/request
M.
Zombie.js is another choice if you want browser-like behaviour. It "maintains state across requests: history, cookies, HTML5 local and session stroage, etc.". More info on zombie's cookie api: http://zombie.labnotes.org/API
There is also PhantomJS and PhantomJS-based frameworks, like CasperJS.
A feature-complete solution for cookies
The self-made solutions proposed in the other answers here don't cover a lot of special cases, can easily break and lack a lot of standard features, such as persistence.
As mentioned by isaacs, the request module now has true cookie support. They provide examples with cookies on their Github page. The examples explain how to enable cookie support by adding a "tough-cookie" cookie jar to your request.
NOTE: A cookie jar contains and helps you manage your cookies.
To quote their Readme (as of April 2015):
Cookies are disabled by default (else, they would be used in
subsequent requests). To enable cookies, set jar to true (either in
defaults or options) and install tough-cookie.
The cookie management is provided through the tough-cookie module. It is a stable, rather feature-complete cookie management tool that implements the "HTTP State Management Mechanism" - RFC 6265. It even offers a variety of options to persist (store) cookies, using a cookie store.
The code below demonstrates using cookie from server side, here's a demo API server that parse cookies from a http client and check the cookie hash:
var express = require("express"),
app = express(),
hbs = require('hbs'),
mongoose = require('mongoose'),
port = parseInt(process.env.PORT, 10) || 4568;
app.configure(function () {
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.static(__dirname + '/public_api'));
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.get('/api', function (req, res) {
var cookies = {};
req.headers.cookie && req.headers.cookie.split(';').forEach(function( cookie ) {
var parts = cookie.split('=');
cookies[ parts[ 0 ].trim() ] = ( parts[ 1 ] || '' ).trim();
});
if (!cookies['testcookie']) {
console.log('First request');
res.cookie('testcookie','testvaluecookie',{ maxAge: 900000, httpOnly: true });
res.end('FirstRequest');
} else {
console.log(cookies['testcookie']);
res.end(cookies['testcookie']);
}
});
app.listen(port);
On the client side, just make a normal request to the server api above, i'm using request module, it by default transfers cookie for each request.
request(options, function(err, response, body) {
console.log(util.inspect(response.headers));
res.render("index.html", {layout: false,user: {
username: req.session.user + body
}});
});
Just get cookies from Set-Cookie param in response headers and send them back with future requests. Should not be hard.
Related
The following warning is being shown in the console, even though I have the following settings on my express application. Has anyone seen this error before? My search brought me to https://github.com/expressjs/express/issues/3095
I am also using express : 4.17.1
let COOKIE_OPTIONS = { httpOnly: true, sameSite: 'None', secure: true };
A cookie associated with a cross-site resource at http://MYURL.URL was set
without the `SameSite` attribute. A future release of Chrome will only deliver
cookies with cross-site requests if they are set with `SameSite=None` and
`Secure`. You can review cookies in developer tools under
Application>Storage>Cookies and see more details at
https://www.chromestatus.com/feature/5088147346030592 and
https://www.chromestatus.com/feature/5633521622188032.
When doing a request using Insomia (Postman) I see the following
access_token=someToken;
Path=/;
HttpOnly;
Secure;
SameSite=None
Documentation Link: https://www.npmjs.com/package/express-session#cookiesamesite
The below code will solve your issue. This is also recommended going forward.
const express = require('express');
const session = require('express-session');
const app = express();
const sessionConfig = {
secret: 'MYSECRET',
name: 'appName',
resave: false,
saveUninitialized: false,
store: store,
cookie : {
sameSite: 'strict', // THIS is the config you are looing for.
}
};
if (process.env.NODE_ENV === 'production') {
app.set('trust proxy', 1); // trust first proxy
sessionConfig.cookie.secure = true; // serve secure cookies
}
app.use(session(sessionConfig));
In your case, set sameSite to 'none'
In case you are wondering what is store? I am using my database as storage for all the cookies. It's not relevant to the question asked by OP. Just added as pointed by #klevis in the comment. Here's the code:
const KnexSessionStore = require('connect-session-knex')(session);
const store = new KnexSessionStore({
tablename: 'session',
knex: kx,
createtable: false
});
Edit 1: Fixed issue pointed out by CaptainAdmin
Edit 2: Added store definition.
You can set these options without using any node package.. With Express Only
Like this:
app.get('/', (req,res)=>{
//.....Other Code
res.cookie('cookieName', 'cookieValue', { sameSite: 'none', secure: true})
//.....Other Code
})
As far I kwon, this is a warning about new implementation for chrome in the future
samesite option on cookies: Starting in Chrome 80, cookies that do not specify a SameSite attribute will be treated as if they were SameSite=Lax with the additional behavior that they will still be included in POST requests to ease the transition for existing sites.
Any further information: https://www.chromium.org/updates/same-site
If you desire to test your web page, this article explains how to set Chrome flags for testing. If your page stops working you have to check all request and see for "http://" to "https://" updates or check third-party cookies
I´ve a problem by saving something in the session above a NodeJs Script. If I start the script and making a post login like this:
app.post('/login', function(req, res) {
sess = req.session;
sess.key = "SecureKEy";
console.log(sess);
});
I got as rusult that what I want:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
key: 'SecureKEy' }
But if I reload the page with this code the session.key is empty. Just like it´s not saved.
app.get('/', function(req, res) {
sess = req.session;
console.log(sess);
res.sendFile(__dirname+'/wwwroot/index.html');
});
My configuration for the express-session is this:
const session = require('express-session');
app.use(session({
secret: 'importent',
resave: true,
saveUninitialized: true
}));
I´ve rewrite the code like this:
app.post('/login', function(req, res) {
console.log("Before: ");
console.log(sess);
sess = req.session;
sess.key = "SecureKEy";
req.session.save();
console.log("After: ");
console.log(sess);
});
With that it work correctly. But if I would resend the logged in page with res.send the session would be automaticly saved? Is that correct?
express-session auto-save edge cases?
The express-session save(...) method is certainly not triggered for some express response transport methods. It seems to trigger consistently for the frequently encountered ones such as response.send(...), response.json(...) etc.
But same is not the case for the special case transport method such as the express.response.end() method - from my observation at least; and also response.sendFile(...) according to the OP and response.redirect(...) according to posts elsewhere.
To avoid unforeseen issue, pay close attention when applying express-session to requests where special case response transport methods were used. The express-session save(...) method may have to be called directly to persist changes made during those requests. Even then, there is no guarantee that persistence would take place.
For example, there are occasions where setting values to null and/or calling the session.destroy(...) and/or session.regenerate(...) methods have no effect. Those destructed session data basically resurface on the next page refresh. Not even calling the save(...) method or setting the unset option to 'destroy' can remedy that situation.
The express-session readme should include these edge case scenarios in one of its Note sections at the top of the page. It would curb some of the headwinds surrounding its auto-save feature.
My philosophy to this type of thing is: when a package is too quirky for a use-case, either find a more suited package or just source your own solution if possible. Workarounds tend to warp application logic thereby making it error prone and difficult to maintain over time.
i followed the sample of authorized-https-endpoint and only added console.log to print the req.cookies, the problem is the cookies are always empty {} I set the cookies using client JS calls and they do save but from some reason, I can't get them on the server side.
here is the full code of index.js, it's exactly the same as the sample:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();
const validateFirebaseIdToken = (req, res, next) => {
console.log(req.cookies); //// <----- issue this is empty {} why??
next();
};
app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
res.send(`Hello!!`);
});
exports.app = functions.https.onRequest(app);
store cookie:
curl http://FUNCTION_URL/hello --cookie "__session=bar" // req.cookies =
{__session: bar}
doesn't store:
curl http://FUNCTION_URL/hello --cookie "foo=bar" // req.cookies =
{}
If you are using Firebase Hosting + Cloud Functions, __session is the only cookie you can store, by design. This is necessary for us to be able to efficiently cache content on the CDN -- we strip all cookies from the request other than __session. This should be documented but doesn't appear to be (oops!). We'll update documentation to reflect this limitation.
Also, you need to set Cache-Control Header as private
res.setHeader('Cache-Control', 'private');
Wow this cost me 2 days of debugging. It is documented (under Hosting > Serve dynamic content and host microservices > Manage cache behavior, but not in a place that I found to be useful -- it is at the very bottom "Using Cookies"). The sample code on Manage Session Cookies they provide uses the cookie name session instead of __session which, in my case, is what caused this problem for me.
Not sure if this is specific to Express.js served via cloud functions only, but that was my use case. The most frustrating part was that when testing locally using firebase serve caching doesn't factor in so it worked just fine.
Instead of trying req.cookies, use req.headers.cookie. You will have to handle the cookie string manually, but at least you don't need to implement express cookie parser, if that's a problem to you.
Is the above answer and naming convention still valid? I can't seem to pass any cookie, to include a session cookie named "__session", to a cloud function.
I setup a simple test function, with the proper firebase rewrite rules:
export const test = functions.https.onRequest((request, response) => {
if (request.cookies) {
response.status(200).send(`cookies: ${request.cookies}`);
} else {
response.status(200).send('no cookies');
}
});
The function gets called every time I access https://www.xxxcustomdomainxxx.com/test, but request.cookies is always undefined and thus 'no cookies' is returned.
For example, the following always returns 'no cookies':
curl https://www.xxxcustomdomainxxx.com/test --cookie "__session=testing"
I get the same behavior using the browser, even after verifying a session cookie named __session was properly set via my authentication endpoint. Further, the link cited above (https://firebase.google.com/docs/hosting/functions#using_cookies) no longer specifies anything about cookies or naming conventions.
I want to implement authentication and authorization in the Flatiron stack (using Flatiron, Resourceful and Restful). I want to require that a user has the necessary permissions, when trying to change a resource. In the Restful Readme file, there's a note about authorization:
There are several ways to provide security and authorization for
accessing resource methods exposed with restful. The recommended
pattern for authorization is to use resourceful's ability for before
and after hooks. In these hooks, you can add additional business logic
to restrict access to the resource's methods.
It is not recommended to place authorization logic in the routing
layer, as in an ideal world the router will be a reflected interface
of the resource. In theory, the security of the router itself should
be somewhat irrelevant since the resource could have multiple
reflected interfaces that all required the same business logic.
TL;DR; For security and authorization, you should use resourceful's
before and after hooks.
So authorization can be handled by Resourceful's hooking system.
My actual problem is the authentication process at the beginning of every HTTP request.
Let's say I have a resource Post, a User and a resource Session. The REST API is being defined by using Restful. My main concern for this question is to ensure that a user has a session when creating a post. Other methods like save, update or for other resources like creating a user should work analogous.
File app.js:
var flatiron = require('flatiron');
var app = flatiron.app;
app.resources = require('./resources.js');
app.use(flatiron.plugins.http);
app.use(restful);
app.start(8080, function(){
console.log('http server started on port 8080');
});
File resources.js:
var resourceful = require('resourceful');
var resources = exports;
resources.User = resourceful.define('user', function() {
this.restful = true;
this.string('name');
this.string('password');
});
resources.Session = resourceful.define('session', function() {
// note: this is not restful
this.use('memory');
this.string('session_id');
});
resources.Post = resourceful.define('post', function() {
this.restful = true;
this.use('memory');
this.string('title');
this.string('content');
});
resources.Post.before('create', function authorization(post, callback) {
// What should happen here?
// How do I ensure, a user has a session id?
callback();
});
There's also a runnable version of the code (thanks #generalhenry).
So assume a user trying to create a post, already has been given a session id, that is sent with every request he makes by a cookie header. How can I access that cookie in the before hook (i.e. the authorization callback)?
The example can be started with node app.js and HTTP requests can be made using curl.
Keep in mind that these guidelines are for authorization process. If you need to use sessionId you can access it either way: req.sessionID, req.cookies["connect.sid"].
By checking requests this way you will be sure every users have valid session id.
app.use(flatiron.plugins.http, {
before: [
connect.favicon(),
connect.cookieParser('catpsy speeds'),
function(req, res) {
if (req.originalUrl === undefined) {
req.originalUrl = req.url;
}
res.emit('next');
},
connect.session({secret : 'secter'}),
function(req, res) {
console.log('Authenticating...');
console.dir(req.session);
//any other validation logic
if (req.url !== '/login' && typeof req.session.user == 'undefined') {
res.redirect('/login');
} else {
res.emit('next');
}
}
]
});
Here is project example using this approach.
I'm trying to set up a basic session system in node. Here's what I've got so far:
app.js:
app.use(express.cookieParser('stackoverflow'));
app.use(express.session());
I'm setting the session data in ajax.js:
addClassToCart: function(req, res) {
req.session.cart = req.body.classId;
console.log(req.session.cart);
}
This logs the correct information. However, when I try to retrieve that information elsewhere (same file, different function):
console.log(req.session.cart);
I get undefined. I feel like I'm missing something incredibly basic. Various tutorials for this are either awful or require me to add in even more packages (something I'm trying to avoid).
More data from my debugging:
This works with non-AJAX requests
The session is set before it's logged.
As it turns out, the issue wasn't with Express' session (as the other answers seem to think). Rather, it was a misunderstanding on my part. I changed addClassToCart to the following:
addClassToCart: function(req, res) {
req.session.cart = req.body.classId;
console.log(req.session.cart);
res.send('class added');
}
Adding res.send() fixed the problem.
As noted in the answer to a related SO question, this can also occur if you're using fetch to get data from your server but you don't pass in the credentials option:
fetch('/addclasstocart', {
method: 'POST',
credentials: 'same-origin' // <- this is mandatory to deal with cookies
})
Cookies won't be passed to the server unless you include this option which means the request's session object will be reset with each new call.
I don't know about basic session store, but redis only took me a few minutes to setup:
app.use(express.session({
store: new RedisStore({
host: cfg.redis.host,
db: cfg.redis.db
}),
secret: 'poopy pants'
}));
On a mac:
brew install redis
app.use(express.session({
secret: "my secret",
store: new RedisStore,
cookie: { secure: false, maxAge:86400000 }
}));
Not sure the problem is in session age, but it just to be safe, I'd suggest you to specify maxAge.