I am receiving an http response with a set-cookie header that I can see in Chrome Devloper tools. I can even see the cookie listed under the network header, however the cookie is not being set in the browser. The browser will set same origin cookies but not ones from CORS requests.
I have tried exposing the header, to find out it is forbidden
I have tried setting withCredentials to true and setting the Access-Control-Allow-Credentials header to true, I am not using a wildcard for my Access-Control-Allow-Origin header and I have the cookie path set to '/'
I tried making this POST request with jQuery, but I could still not console log the set cookie header visible in the Chrome Developer Tools:
How do I make the browser set this cookie?
Here are some relevant parts of my project (Node/Express backend w/ AngularJS frontend):
var cors = require('cors');
app.use(cors({credentials: true, origin: 'http://localhost:8000'})
app.use(session({
secret: 'A_SECRET',
resave: false,
saveUninitialized: false,
store: sessionStore,
cookie: {
secure: false,
path: '/',
domain: 'http://localhost:8000',
maxAge: 1000 * 60 * 24
}
}))
app.use((req,res,next) => {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', 'http://localhost:8000');
next();
})
And in the front end (port 8000) -
angular.module('signIn', [
'core.company',
'signUp'
])
.config(function($httpProvider){
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
$httpProvider.defaults.withCredentials = true;
});
Sign-in component:
$http({
method: 'POST',
url: 'http://xxx.xx.xx.xxx:3000/login',
data: self.user
}).then(function successCallback(response, status, headers, config) {
console.log(response.headers('Set-Cookie')) // logs null
console.log(response.headers('set-cookie')) // logs null
}, function errorCallback(response) {
return response
});
Related
I am running a node.js app on http://localhost:3002 and a client side app on http://localhost:5173 (hosted with vite tooling)
I am trying to send cookies from the server to the client to authenticate users using express-session, but every request that comes in keeps generating as a new session. The cookies are not sending properly.
Node.js code
this.#app.use(cookieParser());
this.#app.use(cors({
origin: WEB_CLIENT_URL,
credentials: true,
methods:'GET, POST',
allowedHeaders:'Origin, X-Requested-With, Content-Type, Accept, authorization'
}));
var sess = {
secret: 'keyboard cat',
saveUninitialized: false,
resave: false,
cookie: {
secret: 'yourSecret',
secure: process.env.NODE_ENV === 'production',
httpOnly: process.env.NODE_ENV === 'production',
sameSite: "none" as "none",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
domain: undefined//'localhost:3002'
},
}
if (process.env.NODE_ENV === 'production') {
this.#app.set('trust proxy', 1) // trust first proxy
}
this.#app.use(session(sess));
this.#app.use(async (req, res, next)=> {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', req.headers.origin ?? "");
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type, Authorization');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', 'true');
const session = req.session as Session;
console.log(req.session.id, req.method, req.originalUrl);
if (!session.user) {
const access = await TokenCollection.CreateAccessToken();
session.user = access.token;
req.session.save((err) => {
if (err) {
next(Utils.GetError('Error creating session', 500));
} else {
next();
}
});
} else {
console.log("FOUND USER");
next();
}
});
and client side
response = await fetch("http://localhost:3002/api/user", {
method: "POST",
body: JSON.stringify(profile_form_data),
headers: {
'authorization': `Bearer ${access.token}`,
'content-type': 'application/json'
},
credentials: 'include'
});
BTW im running in dev mode so process.env.NODE_ENV === 'production' will be false.
Does anyone know what's wrong?
I have created 2 herokuapps, both sharing the herokuapp.com as the main domain, however when I want to set cookie from one to another it does not allow me, I also tested this with ngrok and the result is the same.
It returns "This Set-Cookie was blocked because its Domain attribute was invalid with regards to the current host url"
here is my backend code:
const express = require("express");
const app = express();
const cors = require("cors");
const cookieParser = require("cookie-parser");
app.use(cookieParser());
app.use(
cors({
origin: [process.env.FRONT_URL], // {my-frontend}.herokuapp.com
methods: ["GET", "PUT", "POST"],
allowedHeaders: ["Content-Type", "Authorization", "x-csrf-token"],
credentials: true,
maxAge: 600,
exposedHeaders: ["*", "Authorization"],
})
);
app.get(
"/protect-me",
function (req, res, next) {
if (req.cookies["access_token"] == "accesstoken") next();
else return res.status(401).send("Unauthorized");
},
function (req, res, next) {
res.json({ msg: "user get" });
}
);
app.post("/login", function (req, res, next) {
res.cookie("access_token", "accesstoken", {
expires: new Date(Date.now() + 3600 * 1000 * 24 * 180 * 1), //second min hour days year
secure: true, // set to true if your using https or samesite is none
httpOnly: true, // backend only
sameSite: "none", // set to none for cross-request
domain: process.env.COOKIE_DOMAIN, // tested both with .herokuapp.com & herokuapp.com
path: "/"
});
res.json({ msg: "Login Successfully" });
});
app.listen(process.env.PORT, function () {
console.log("CORS-enabled web server listening on port 80");
});
then on frontend I first try to login with codes below from {my-frontend}.herokuapp.com:
fetch('https://{my-backend}.herokuapp.com/login', {
method: 'POST', credentials: 'include'
});
and then making the second request from {my-frontend}.herokuapp.com:
fetch('https://{my-backend}.herokuapp.com/protect-me', {
credentials: 'include'
});
Thank you in advance for your attention :)
Additional Note
Just as a side note, this works perfectly fine when we have a root domain and subdomain communication, what I mean is, if for example your auth server is on yourdomain.com, then your dashboard is on dashboard.yourdomain.com, then you can easily set a .yourdomain.com cookie and all works fine
but it is not possible for me to make a cookie with auth.yourdomain.com for .yourdomain.com so that the dashboard.yourdomain.com can access it as well
I think the cookie domain should be same as that of frontend url thats what the error is also saying
I am trying to make a third party application meaning it will run across multiple domains.
I want to handle a session per user that uses the app, therefore, I used the express-session module to make it but every time I make a request it starts up a new session for the current request...
const express = require('express'),
router = express.Router();
const session = require('express-session')
router.use(function(req, res, next) {
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
next();
});
router.use(session({
secret: 'keyboard cat',
resave: true,
maxAge: 2 * 60 * 60 * 1000, // 2 hours
saveUninitialized: false,
cookie: {
maxAge: 2 * 60 * 60 * 1000 ,
secure: false,
sameSite : false,
httpOnly: false}
}))
router.get( '/',function (req, res, next) {
// let payload = req.query;
let isDevClient = req.session.isDevClient || false;
console.log('isNew? ', isDevClient );
res.status(201).send({
success: true,
isDevClient,
message: 'msg..'
});
}).post( '/',function (req, res, next) {
let payload = req.body;
console.log('isNew? ', req.session.isDevClient )
req.session.isDevClient = true;
res.status(200).send({
success: true,
message: 'ok'
});
});
module.exports = router;
Request example
// javascript
fetch('https://127.0.0.1:8443/',{
method : "POST",
credentials: 'include',
})
//Jquery
$.ajax({
'type': 'post',
'url': 'https://127.0.0.1:8443',
'xhrFields': {
'withCredential's: true
}
'success': function (response) {},
})
``
Use credentials: 'include' in your fetch call, otherwise fetch won't send cookies during cross-domain request. Example:
fetch(..., {
...,
credentials: 'include'
}
Update: seems like recent Chrome version will not send cookies during cross-domain requests if SameSite attribute is not set.
Setting sameSite : 'none' should fix it. Note that chrome also requires these cookies to be Secure. https://www.chromestatus.com/feature/5633521622188032
By the way, you can easily provide examples with repl.it (like this)
I have an app in nodejs that will serve as a proxy to connecting to various social platforms. The flow is like this:
Click on a button, open a new window
Before closing a window, add access token to cookie (for now the app is on localhost, so the token is on that domain) as a nonce and add it to database.
Once the modal closes, go to another endpoint that will take that nonce from cookie, search in db, and return the token.
Here is the issue, after sending AJAX request for step 3, CORS issue occurs. This is the code:
jQuery.ajax({
url: "http://localhost:9057/facebook/gettoken",
type: 'GET',
dataType: "json",
xhrFields: {
// -->> In order to access the cookie, we have to have this set as true.
withCredentials: true
},
crossDomain: true,
success: function (res) {
console.log(res);
}
});
In my NodeJS app, I have cors set up as:
if (config.getOption('PORT')) {
const corsOptions = {
credentials: true
};
app.use(cors(corsOptions));
// -->> I cannot have * here here, withCredentials cannot be set to true and have *, see the error below
app.options('*', cors(corsOptions));
}
This is the error:
Access to XMLHttpRequest at 'http://localhost:9057/facebook/gettoken' from origin 'http://something.test:8080' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
I cannot whitelist the domains that 'http://something.test:8080' represents as they will be user websites.
Anyone knows a workaround, if there is one?
See the docs.
They give an example of how to use a dynamic origin:
var whitelist = ['http://example1.com', 'http://example2.com']
var corsOptions = {
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
If you can't whitelist, then just remove the test for the whitelist!
var corsOptions = {
origin: function (origin, callback) {
callback(null, true)
}
}
So, I've made a Node.js API using the express framework. The API supports a POST-request to /login, where the client should include email and password formatted as json in the body. The API will then return a session cookie via the setCookie-header.
I DO see the cookie coming back from the API as a response-cookie, however, the browser isn't storing it, and therefore it is not sent with further requests from the client. I've tried using {credentials: include} since this is a CORS-request. I've also added the cors-module in my node-server (API) to handle the OPTIONS (pre-flight) requests. I've used so many hours trying to figure this out, so any help would be much appreciated.
Side-note: This works completely fine in both Postman and a prototype iOS-app I've developed using the same API, so there shouldn't be any issues on the server itself.
I've included relevant code from the server and the front-end below.
Code from server:
app.use(cors({credentials: true, origin: ['http://expivider.dk', 'http://expivider.herokuapp.com', 'https://expivider.herokuapp.com', 'http://api.expivider.dk']}));
app.use(session({
cookieName: 'session',
secret: SECRET_HERE,
duration: 30 * 60 * 1000,
activeDuration: 5 * 60 * 1000,
cookie: {
// path: '/api', cookie will only be sent to requests under '/api'
maxAge: 60000, // duration of the cookie in milliseconds, defaults to duration above
ephemeral: false, // when true, cookie expires when the browser closes
httpOnly: false, // when true, cookie is not accessible from javascript
secure: false, // when true, cookie will only be sent over SSL. use key 'secureProxy' instead if you handle SSL not in your node process
path: "/"
//domain: "expivider.herokuapp.com"
}
}));
Code from front-end:
const handleRequestWithBodyWithCredentials = function (method, url, body, callback) {
fetch(url, {
method: method,
credentials: 'include',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(body),
mode: 'cors'
}).then((resp) => (resp.json())).then(function (data) {
callback(data);
});
};
const validate = function () {
let em = document.login.username.value;
let pw = document.login.password.value;
let body = {
'email': em,
'password': pw
};
handleRequestWithBodyWithCredentials('post', LOGIN_NEW, body, showCompanyStats);
console.log();
};
Note: Right now, the front-end is hosted on 'http://expivider.dk', and it makes calls to the api at 'http://api.expivider.dk' (which is actually hosted at 'expivider.herokuapp.com' but I'm using a custom-domain).
Please let me know if you need any more info to help me out!