I have a dead simple http nodejs server:
require('http').createServer(function(req, res) {
res.statusCode = 200;
res.setHeader('Set-Cookie', 'age=44; name=ok; something=else; path=/');
res.end('ok ok ok', 'utf8');
}).listen(9999);
Upon visiting the page in the latest version of Chrome, these are the Response Headers sent from the server:
So, the server sends the correct cookies. However, the browser only seems to store the first one (age=44). If I refresh the page again, these are the Request Headers being sent to the server:
Only the first cookie (age=44) is sent. Logging document.cookie in the console also returns just 'age=44'.
When inspecting the cookies from the Chrome's UI, I can also see that only the first one is saved:
What's the problem here?
If you're just using vanilla Node then you can simply pass an array as the second parameter to res.setHeader(field, value):
res.setHeader('Set-Cookie', [
'age=44; path=/',
'name=ok; path=/',
'something=else; path=/'
])
If using Express 4.11.0+ you can use res.append(field [, value]):
res.append('Set-Cookie', 'age=44; path=/');
res.append('Set-Cookie', 'name=ok; path=/');
res.append('Set-Cookie', 'something=else; path=/');
Related
I am working on sample application using Node.js for server side and Angular 2 for front end.
To prevent CSRF attacks , I have used "csurf" middleware
Below is the relevant code to set the middleware
// cookie parser
app.use(cookieParser());
// express session middleware , this should be after cookie parser
app.use(session({secret:'clickclick'}));
app.use(session({
secret: 'clickclick',
cookie: {
path:'/',
httpOnly:true,
maxAge:null
}
}));
// CSRF middleware
app.use(csurf());
Below node.js route sets "_csrf" header
router.get('/:id/products/:pid' , wrap(function *(req , res , next) {
try
{
console.log('url' , req.url);
res.setHeader('_csrf', req.csrfToken());
let product = yield category.getProduct(req , res , next);
res.send(product);
}
catch(err)
{
res.status(500).send(err);
}
}))
The above mentioned route '/:id/products/:pid' is called from my below Angular 2 service method
// Get Product
GetProduct(id:string, pid:string):Observable<Product> {
return this.http.get('./categories/' + id + '/products/' + pid)
.map(data =>{ let headers:Headers = data.headers;
this.csrfToken = headers.get('_csrf') ;
return data.json() })
.catch(this.handleError);
}
This method assigns the _csrf header returned from server to "this.csrfToken" property.
And when the below service method makes an AJAX POST request , it uses the "this.csrfToken" property value set by above method and sets header "_csrf" value.
// Add an item to cart
AddTocart(product:Product)
{
let item = { pid:product._id , name:product.name , price:product.price , qty:1 , total:product.price };
//this.cart.push(item);
// make an AJAX call to save the item in server session
let url = './cart/add';
let headers = new Headers({'Content-Type':'application/json' , '_csrf':this.csrfToken});
let requestOptions = new RequestOptions({headers:headers});
this.http.post(url , item , requestOptions)
.map(data => {
this.cart.push(item);
}
)
.catch(this.handleError)
.subscribe( data => { });
}
Below is the Response Header of GetProduct service method.
And below is the request Header of "AddTocart" service method.
Any idea what is causing "ForbiddenError: invalid csrf token" error.
Please let me know if I need to provide more information or if the information provided is not clear.
I know this is an older question, but I'm adding this here in case someone stumbles across it in the future. Working on a similar project and encountered the same error, I fixed it by adding a XSRF-TOKEN header in the POST request, with the value taken from $.cookie("XSRF-TOKEN") (using jquery and the cookies plugin). According to the docs, _csrf should also work though.
From the project page :
The default value is a function that reads the token from the following locations, in order:
req.body._csrf - typically generated by the body-parser module.
req.query._csrf - a built-in from Express.js to read from the URL query string.
req.headers['csrf-token'] - the CSRF-Token HTTP request header.
req.headers['xsrf-token'] - the XSRF-Token HTTP request header.
req.headers['x-csrf-token'] - the X-CSRF-Token HTTP request header.
req.headers['x-xsrf-token'] - the X-XSRF-Token HTTP request header.
As far as I can tell, the error seems to come from POST / PUT requests including the correct cookies, but nodejs / csurf isn't looking for them there.
In your specific case, _csrf should be in the request body along with the cart items, or the header should be renamed to csrf-token, or one of the other options.
I'm using supertest and trying to get the cookie data cleanly
agent.post('/login').send('username:u','password:p').end(function(err, res) {
agent.cookie = res.req._headers.cookie;
});
But to cookie data I get is jumbled up
connect.sid=s%3Afc20GaiFXGNju1rxqDSOEiA4.W3XfTacI0xhk4gQ9Bdh1LLC%2FlhjnAwVmQkUX%2F7%2FJEAo
instead of cleanly like this (and the above is even longer for some reason)
{ 'connect.sid': 'T0jrnU09DzCYN68FsgspuY5g' }
How do I get the latter?
require('cookie').parse(decodeURIComponent(cookie))
PhantomJs's webserver does not support multipart requests, so I'm trying to send a single-part request from NodeJs.
Unfortunatly the nodejs example looks to be multipart. is there any way of doing this with NodeJs?
http://nodejs.org/api/http.html#http_http_request_options_callback
edit:
in the nodejs docs it mentions:
Sending a 'Content-length' header will disable the default chunked encoding.
but unfortunatly it's still multi-part, just not multi-multipart :P
edit2: for showing code, it's a bit hard to show a distilled example, but here goes:
node.js code (it's Typescript code):
```
//send our POST body (our clientRequest)
var postBody = "hello";
var options : __node_d_ts.IRequestOptions = {
host: host,
port: port,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-length": postBody.length
}
};
//logger.assert(false);
var clientRequest = http.request(options,(response: http.ServerResponse) => {
//callback stuff here
});
clientRequest.on("error", (err) => {
thisObj.abort("error", "error,request error", err);
});
//clientRequest.write();
clientRequest.end(postBody);
```
when i read the results from PhantomJS, the post/postRaw fields are null.
when I use a tool like the Chrome "Advanced REST Client" extension to send a POST body, phantomjs gets it no problem.
i don't have a network sniffer, but as described here, it says phantomjs doesnt work with multipart so I think that's a good guesss: How can I send POST data to a phantomjs script
EDIT3:
indeed, here's the request phantomjs gets from my chrome extension (valid post)
//cookie, userAgent, and Origin headers removed for brevity
{"headers":{"Accept":"*/*","Accept-Encoding":"gzip,deflate,sdch","Accept-Language":"en-US,en;q=0.8,ko;q=0.6","Connection":"keep-alive","Content-Length":"5","Content-Type":"application/json","DNT":"1","Host":"localhost:41338", "httpVersion":"1.1","method":"POST","post":"hello","url":"/"}
and here's the request phantomjs gets from the nodejs code i show above:
//full request, nothing omitted!
{"headers":{"Connection":"keep-alive","Content-Type":"application/json","Content-length":"5","Host":"10.0.10.15:41338"},"httpVersion":"1.1","method":"POST","url":"/"}
I'm doing cookie session management with express with something like this:
req.session.authentication = auth;
And I verify the authenticated urls with something like
if(!req.session.authentication){res.send(401);}
Now I'm building tests for the URLs with mocha, superagent and should, however I can't seem to find a way to get/set the cookie with superagent. I even tried to request the login before the authenticated test but it is not working,
I have tried adding the request to the login in the before statement for the mocha BDD suite, however it is still telling me that the request is unauthorized, I have tested the authentication doing the requests from the browser, however it is not working from the suite any ideas why?
Use superagent.agent() (instead of plain old superagent) to make requests have persistent cookies. See 'Saving cookies' in the superagent docs, or the code examples: agency.js, controller.test.js.
Seems like following code works fine;
req.set('Cookie', "cookieName1=cookieValue1;cookieName2=cookieValue2");
If the issue is in sending cookies for CORS requests use .withCredentials() method
described here
request
.get('http://localhost:4001/')
.withCredentials()
.end(function(err, res) { })
Since you mentioned you need to both get and set the cookie:
Get:
const request = await Superagent.get('...')
const cookie = request.header['set-cookie']
Set:
Superagent.post('...').set('Cookie', 'cookie_info')
2020 +
A clean way to do it is:
create a simple cookie store
abstract set Cookie to send it in each request
update the cookie only when needed
Note I keep the same URL because I use graphql but you can make it a parameter:
const graph = agent =>
agent.post('/graph')
.set('cookie', cookieStore.get());
const handleCookie = res =>
cookieStore.set(res.headers['set-cookie'][0]);
let currentCookie="";
const cookieStore = {
set: cookie=>{currentCookie=cookie},
get: cookie=>currentCookie,
};
module.exports = {graph,connectTestUser,handleCookieResponse};
You can now just use graph(agent) to send a request and handleCookie(response) when you have a response that may update your cookie (set or clear), example:
graph(agent).end((err,res) => {
if (err) return done(err);
res.statusCode.should.equal(200);
handleCookie(res);
return done();
});
Add a cookie to agent cookiejar:
const request = require('superagent');
const {Cookie} = require('cookiejar')
const agent = request.agent()
agent.jar.setCookie(new Cookie("foo=bar"))
I'm working on an application which allows you to upload images, and create albums. Everything works fine accept that after an album is created the new album isn't shown in the client until the page is reloaded, and I can't figure out how to solve this.
Below is the route for creating an album. Is it somehow possible to use something else than res.redirect('back') in this case so that the page is reloaded after the route has finished?
Of course I could copy/paste the code from the route for loading the page in the first place, but that would be to much DRY. Maybe I can call the other route from within this route?
route:
app.post('/album', function(req, res){
var newAlbum = new albumModel.Album();
newAlbum.imageName = req.body.albumPicsName;
newAlbum.imageId = req.body.albumPicsId;
newAlbum.title = req.body.albumTitle;
newAlbum.save(function (err) {
if (err) {
console.log(err);
// do something
console.trace();
}
res.redirect('back');
});
});
'back' is an alias for req.get('Referrer') so if '/albums' is your referrer you might still experience issues with the browser returning a 304 (http not modified) http status or a browser cached page (common). If you experience that issue you can do a couple of things:
Send some cache clearing headers:
res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0');
res.redirect('back');
Or modify the url:
var url = require('url');
var u = url.parse(req.get('Referrer'), true, false);
u.query['v'] = +new Date(); // add versioning to bust cache
delete u.search;
res.redirect(url.format(u));
Of course if you know the url such as '/albums' you don't have to go though all that rigamarole to add some versioning - just append current timestamp to the url string.
The first way is cleaner and works better imo. I've seen cases when a page doesn't have a referrer even though I clearly came from another page due to caching.
You should be able to do res.redirect('/album') instead of back to force a full reload but get the same type of feedback.