Cookie in Set-Cookie header not being set - node.js

I'm sending a request to a node.js server from a reactjs client using axios as shown below.
import axios from 'axios';
const apiClient = axios.create({
withCredentials: true,
baseURL: 'http://localhost:8080'
});
async function(payload) {
try {
debugger;
let result = await apiClient.post('/auth/signup/', payload);
debugger;
return result;
} catch (error) {
debugger;
throw error;
}
}
The node.js endpoint sets a cookie in the response as shown below.
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser')
const cors = require('cors');
const jwt = require('jsonwebtoken');
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
router.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['Set-Cookie', 'Date', 'ETag']} ));
router.use(cookieParser());
router.post('/signup', async (req, res, next) => {
debugger;
let database = req.app.locals.database;
try {
let user = await database.findByUsername(req.body.username);
let token = await jwt.sign({username: user.username}, config.secret, {expiresIn: "15m"});
res.cookie('jwt', token, {
maxAge: 900,
});
} catch (error) {
debugger;
return res.status(503).send({ auth: false, message: 'Database error.' });
}
});
The Set-Cookie header of the response contains the cookie as expected.
However, Chrome does not appear to be setting the cookie, as I cannot see the cookie in the Application window of the Developer Console.
I've looked at the answers to the following questions, which mention setting { withCredentials: true } in the axios configuration and not using a wildcard origin for cors in node.js, but I am already doing both.
Set-Cookie header not setting cookie in Chrome
Set cookies for cross origin requests
Any ideas as to why the cookie is not being set and how to fix this issue?

Though you are hosting client and server in the same domain as http://localhost, your ports are different, so the same-origin policy is failed here. You can check https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy.
As so, you making a CORS request, check your network tab in your developer tools in your current browser, you might see a preflight request OPTIONS, before your client sends POST request to your server.
The server must specify headers to accept the origin of your next request - POST request from http://localhost:8000 with method POST, you can refer to https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST // Your next request will use POST method
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true // cookies accepted
Added:
In Set-Cookie, Max-Age must be non-zero digit. It be rounded up into integer according to RFC doc. For express.js, cookies `maxAge property is on the scale of miliseconds
The solution will be set the maxAge property as second * 1000
res.cookie('jwt', token, {
maxAge: 10000,
});

Here's a repost of my answer on a similar question https://stackoverflow.com/a/62821342/8479303
In my case, the network panel showed that the response had the 'Set-Cookie' header, but in axios the header wouldn't show up, and the cookie was being set.
For me, the resolution was setting the Access-Control-Expose-Headers header.
For explanation, from this comment on an issue in the axios repository I was directed to this person's notes which led me to set the Access-Control-Expose-Headers header -- and now the cookie is properly setting in the client.
So, in Express.js, I had to add the exposedHeaders option to my cors middleware:
const corsOptions = {
//To allow requests from client
origin: [
"http://localhost:3001",
"http://127.0.0.1",
"http://104.142.122.231",
],
credentials: true,
exposedHeaders: ["set-cookie"],
};
...
app.use("/", cors(corsOptions), router);
It was also important that on the axios side I use the withCredentials config in following axios requests that I wanted to include the cookies.
ex/
const { data } = await api.get("/workouts", { withCredentials: true });

Related

React - httpOnly cookies not getting set from the express server

Vite + React: http://localhost:5173
Express: http://localhost:3000
Here is the code in the express server's login route:
const mycookie = cookie.serialize("jwt", refreshToken, {
httpOnly: true, // Set the HTTP-only flag
secure: true, // Set the secure flag
sameSite: "none",
path: "/", // Set the path of the cookie to '/'
maxAge: 3600, // Set the maximum age of the cookie to 1 hour
});
// Set the cookie in the response headers
res.setHeader("Set-Cookie", mycookie);
res.json({
accessToken,
refreshToken,
});
Here is my cors config:
const allowedOrigins = require("./allowedOrigins");
const corsOptions = {
crendials: true,
origin: function (origin, callback) {
console.log(allowedOrigins.indexOf(origin));
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
};
Here is my handleSubmit function in the frontend (gets called when the user clicks submit):
const handleSubmit = () => {
console.log("Login");
// axios.defaults.withCredentials = true;
axios.post("http://localhost:3000/api/v1/auth/login", { email, password });
};
Whenever the request is made to the server. The response header does contain the set-Header to set the jwt token but I am not able to see it in my applications tab under cookies in devtools. A pre-flight request also comes in which probably clears the cookie.
My networks tab:
The xhr request:
The OPTIONS request:
The Applications tab:
However, when I disable CORS in the browser, the cookie is getting set.
Networks tab (NOTICE: No PreFlight request)
Applications tab:
I tried working out different answers from stackoverflow answers like this, this, this, and many more along with a reddit post on a similar issue but nothing has worked.
P.S. : I already tried using credentials: true

Nextjs not reading cookie from Express response all in localhost

In my local environment, I'm trying to set up user tokens after login in my NextJS app (localhost:3005), using the response from my express backend (localhost:3020). I can see set-cookie in the response on the server, but the cookies in the getServerSideProps is empty always.
My code is pretty basic:
Express backend
// Cors set up
cors({
methods: "GET,HEAD,OPTIONS,POST",
preflightContinue: false,
credentials: true,
origin: [
"http://localhost:3005",
...
],
optionsSuccessStatus: 204,
})
// Response - can see this cookie in set-cookie
return res.cookie("test", "test1", {
httpOnly: true,
sameSite: "none",
expires,
domain: "localhost:3005", // tried without this also
}).redirect("http://localhost:3005/login");
My NextJS app has the following:
//Login component : on login submit
const resp = await fetch(
`${BACKEND_URL}login?originalUrl=/login`, // Redirecting back
{
headers: new Headers({
Authorization: "Bearer " + someToken,
}),
credentials: "include",
method: "POST",
redirect: "follow", // Tried this but did not work
}
);
// resp.redirected is true
// Login component - triggers correctly but no cookies
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { req } = ctx;
const { cookies } = req;
console.log("cookies", cookies ); // Always {}
return { props: {} };
};
I have been stuck with this for a while now guys, it seems like I'm missing something very trivial here. I'm not super familiar with NextJs, so any help here would be greatly appreciated. Thanks!
This was happening because of the Chrome SameSite=None + Secure requirement. Setting secure to true in the cookie resolved this issue. I did not have to set up SSL certificates for my local environment, just setting secure worked.

Express JS cors middleware unable to store cookies on browser

I want the react application to be able to store cookies in the cookie storage of browser.
I am required to send withCredentials: true in headers in axios.
axios.defaults.withCredentials = true
export const login = (email, password) => {
return axios({
method: "POST",
url: "https://api.*****.**/login",
data: {
email: email,
password: password,
},
});
};
On the backend I am using ExpressJS cors middleware
const cors = require("cors");
const corsOptions = {
credentials: true,
};
app.use(cors(corsOptions));
On making login request from react using axios, in response I am getting error:
Access to XMLHttpRequest at 'https://api.*****.**/login' from origin 'http://localhost:3000' has been blocked by CORS policy:
Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
From res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true }) remove domain in option

How to fix "Response to preflight request doesn't pass access control check: It does not have HTTP ok status" error in react app with nodejs api

I have been trying to do an api call (nodejs with express running on localhost) from a react app running in the browser over a local dev server (web-pack dev server). Everything was working well until I tried to call the api. They are both running on separate ports.
I have tried adding the cors headers (Recommended by MDN) to both the post call (from the app in browser) and to the response from the Nodejs API but neither of these solved the issue.
Code for the api call (in browser):
const headers = {
'Content-Type': 'application/json',
'access-token': '',
'Access-Control-Allow-Origin': '*',
}
export default async () => {
try {
const body = JSON.stringify({
test: true,
})
const response = await fetch('http://localhost:1337/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
console.log(response)
} catch (e) {
return e
}
}
API Middleware (in nodejs):
// Verify All Requests
app.use(VerifyToken)
// Compress
app.use(compression())
// Helmet middlware
app.use(helmet())
// Body Parser
app.use(bodyParser.urlencoded({
extended: false,
}))
app.use(bodyParser.json())
The expected result is to just give a 200 status code and respond with the data.
The actual output is:
OPTIONS http://localhost:1337/internal/provider/check_email_exist 404 (Not Found)
Access to fetch at 'http://localhost:1337/internal/provider/check_email_exist' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Since you're using webpack-dev-server you can use the proxy option DevServerProxy.
Your configuration will look like this:
// webpack.config.js
devServer: {
proxy: {
'/internal': 'http://localhost:1337'
}
}
Since I can't see your express routes on your question I'm speculating about the proxy route if your API lives on /internal endpoint then you should modify your React code like this:
const response = await fetch('/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
As you can see I ommited the https://localhost:1337 because the proxy option from webpack-dev-server will handle this and it will redirect to http://localhost:1337. Hope this will help you. Cheers, sigfried.
EDIT
As the comment on your question pointed out you should set the headers on your express server, not the client, for this task you can use the cors-middleware package.
Maybe this can help if you face with preflight errors.
My full config:
const cors = require('cors');
const express = require('express');
const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
...
const logLevel = 'info';
const ip = require('ip').address();
const proxyOptions = {
xfwd: true,
target,
changeOrigin: true,
logLevel,
cookieDomainRewrite: {
'*': 'localhost',
},
headers: {
'X-Forwarded-For': ip,
'X-Node': 'true',
},
};
const backNginxApp = express();
backNginxApp.use(
cors({
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true,
})
);
backNginxApp.use('/api', proxy(proxyOptions));
API: const target = 'https://someapi.com'
Local development running at: http://localhost:3000

Express doesn't set a cookie

I have problem with setting a cookies via express. I'm using Este.js dev stack and I try to set a cookie in API auth /login route. Here is the code that I use in /api/v1/auth/login route
res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999)});
res.status(200).send({user, token: jwt.token});
In src/server/main.js I have registered cookie-parser as first middleware
app.use(cookieParser());
The response header for /api/v1/auth/login route contains
Set-Cookie:token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJ..
but the cookie isn't saved in browser (document.cookie is empty, also Resources - Cookies tab in develepoers tools is empty) :(
EDIT:
I'm found that when I call this in /api/v1/auth/login (without call res.send or res.json)
res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false});
next();
then the cookie is set AND response header has set X-Powered-By:Este.js ... this sets esteMiddleware in expres frontend rendering part.
When I use res.send
res.cookie('token', jwt.token, {expires: new Date(Date.now() + 9999999), httpOnly: false}).send({user, token: jwt.token});`
next();
then I get error Can't set headers after they are sent. because send method is used, so frontend render throw this error.
But I have to send a data from API, so how I can deal with this?
I had the same issue. The server response comes with cookie set:
Set-Cookie:my_cookie=HelloWorld; Path=/; Expires=Wed, 15 Mar 2017 15:59:59 GMT
But the cookie was not saved by a browser.
This is how I solved it.
I use fetch in a client-side code. If you do not specify credentials: 'include' in fetch options, cookies are neither sent to server nor saved by a browser, although the server response sets cookies.
Example:
var headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
return fetch('/your/server_endpoint', {
method: 'POST',
mode: 'same-origin',
redirect: 'follow',
credentials: 'include', // Don't forget to specify this if you need cookies
headers: headers,
body: JSON.stringify({
first_name: 'John',
last_name: 'Doe'
})
})
Struggling with this for a 3h, and finally realized, with axios, I should set withCredentials to true, even though I am only receiving cookies.
axios.defaults.withCredentials = true;
I work with express 4 and node 7.4 and Angular, I had the same problem this helped me:
a) server side: in file app.js I give headers to all responses like:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
This must have before all routers.
I saw a lot of added this header:
res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
but I don't need that.
b) when you define cookie you need to add httpOnly: false, like:
res.cookie( key, value,{ maxAge: 1000 * 60 * 10, httpOnly: false });
c) client side: in send ajax you need to add: withCredentials: true, like:
$http({
method: 'POST',
url: 'url',
withCredentials: true,
data : {}
}).then(function(response){
// do something
}, function (response) {
// do something else
});
There's a few issues:
a cookie that isn't explicitly set with httpOnly : false will not be accessible through document.cookie in the browser. It will still be sent with HTTP requests, and if you check your browsers' dev tools you will most likely find the cookie there (in Chrome they can be found in the Resources tab of the dev tools);
the next() that you're calling should only be used if you want to defer sending back a response to some other part of your application, which—judging by your code—is not what you want.
So, it seems to me that this should solve your problems:
res.cookie('token', jwt.token, {
expires : new Date(Date.now() + 9999999),
httpOnly : false
});
res.status(200).send({ user, token: jwt.token });
As a side note: there's a reason for httpOnly defaulting to true (to prevent malicious XSS scripts from accessing session cookies and the like). If you don't have a very good reason to be able to access the cookie through client-side JS, don't set it to false.
I had the same issue with cross origin requests, here is how I fixed it. You need to specifically tell browser to allow credentials. With axios, you can specify it to allow credentials on every request like
axios.defaults.withCredentials = true
however this will be blocked by CORS policy and you need to specify credentials is true on your api like
const corsOptions = {
credentials: true,
///..other options
};
app.use(cors(corsOptions));
Update: this only work on localhost
For detail answer on issues in production environment, see my answer here
I was also going through the same issue.
Did code changes at two place :
At client side :
const apiData = await fetch("http://localhost:5000/user/login",
{
method: "POST",
body: JSON.stringify(this.state),
credentials: "include", // added this part
headers: {
"Content-Type": "application/json",
},
})
And at back end:
const corsOptions = {
origin: true, //included origin as true
credentials: true, //included credentials as true
};
app.use(cors(corsOptions));
Double check the size of your cookie.
For me, the way I was generating an auth token to store in my cookie, was causing the size of the cookie to increase with subsequent login attempts, eventually causing the browser to not set the cookie because it's too big.
Browser cookie size cheat sheet
There is no problem to set "httpOnly" to true in a cookie.
I am using "request-promise" for requests and the client is a "React" app, but the technology doesn't matter. The request is:
var options = {
uri: 'http://localhost:4000/some-route',
method: 'POST',
withCredentials: true
}
request(options)
.then(function (response) {
console.log(response)
})
.catch(function (err) {
console.log(err)
});
The response on the node.js (express) server is:
var token=JSON.stringify({
"token":"some token content"
});
res.header('Access-Control-Allow-Origin', "http://127.0.0.1:3000");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header( 'Access-Control-Allow-Credentials',true);
var date = new Date();
var tokenExpire = date.setTime(date.getTime() + (360 * 1000));
res.status(201)
.cookie('token', token, { maxAge: tokenExpire, httpOnly: true })
.send();
The client make a request, the server set the cookie , the browser (client) receive it (you can see it in "Application tab on the dev tools") and then I again launch a request to the server and the cookie is located in the request: "req.headers.cookie" so accessible by the server for verifying.
I had same problem in Angular application. The cookies was not set in browser although I used
res.cookie("auth", token, {
httpOnly: true,
sameSite: true,
signed: true,
maxAge: 24 * 60 * 60 * 1000,
});
To solve this issue, I added app.use(cors({ origin:true, credentials:true })); in app.js file of server side
And in my order service of Angular client side, I added {withCredentials: true} as a second parameter when http methods are called like following the code
getMyOrders() {
return this.http
.get<IOrderResponse[]>(this.SERVER_URL + '/orders/user/my-orders', {withCredentials: true})
.toPromise();}
vue axios + node express 2023
server.ts (backend)
const corsOptions = {
origin:'your_domain',
credentials: true,
optionSuccessStatus: 200,
}
auth.ts (backend)
res.cookie('token', JSON.stringify(jwtToken), {
secure: true,
httpOnly: true,
expires: dayjs().add(30, "days").toDate(),
sameSite: 'none'
})
authService.ts (frontend)
export class AuthService {
INSTANCE = axios.create({
withCredentials: true,
baseURL: 'your_base_url'
})
public Login = async (value: any): Promise<void> => {
try {
await this.INSTANCE.post('login', { data: value })
console.log('success')
} catch (error) {
console.log(error)
}
}
it works for me, the cookie is set, it is visible from fn+F12 / Application / Cookies and it is inaccessible with javascript and the document.cookie function. Screenshot Cookies Browser
One of the main features is to set header correctly.
For nginx:
add-header Access-Control-Allow-Origin' 'domain.com';
add_header 'Access-Control-Allow-Credentials' 'true';
Add this to your web server.
Then form cookie like this:
"cookie": {
"secure": true,
"path": "/",
"httpOnly": true,
"hostOnly": true,
"sameSite": false,
"domain" : "domain.com"
}
The best approach to get cookie from express is to use cookie-parser.
A cookie can't be set if the client and server are on different domains. Different sub-domains is doable but not different domains and not different ports.
If using Angular as your frontend you can simply send all requests to the same domain as your Angular app (so the app is sending all API requests to itself) and stick an /api/ in every HTTP API request URL - usually configured in your environment.ts file:
export const environment = {
production: false,
httpPhp: 'http://localhost:4200/api'
}
Then all HTTP requests will use environment.httpPhp + '/rest/of/path'
Then you can proxy those requests by creating proxy.conf.json as follows:
{
"/api/*": {
"target": "http://localhost:5200",
"secure": false,
"changeOrigin": true,
"pathRewrite": {
"^/api": ""
}
}
}
Then add this to ng serve:
ng serve -o --proxy-config proxy.conf.json
Then restart your app and it should all work, assuming that your server is actually using Set-Cookie in the HTTP response headers. (Note, on a diff domain you won't even see the Set-Cookie response header, even if the server is configured correctly).
Most of these answers provided are corrections, but either of the configuration you made, cookies won't easily be set from different domain. In this answer am assuming that you are still in local development.
To set a cookie, you can easily use any of the above configurations or
res.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']); // setting multiple cookies or
res.cookie('token', { maxAge: 5666666, httpOnly: true })
Both of the will set your cookie while to accessing your cookie from incoming request req.headers.
In my case, my cookie were not setting because my server was running on http://localhost:7000/ while the frontend was running on http://127.0.0.1:3000/ so the simple fix was made by making the frontend run on http://localhost:3000 instead.
I struggle with it a lot so follow below solution to get through this
1 check if you are getting token with response with postmen in my case i was getting token in postmen but it wasn't being saved in cookies.
I was using a custom publicRequest which looks like below
try {
const response = await publicRequest.post("/auth/login", user, {withCredentials: true});
dispatch(loginSuccess(response.data));
} catch (error) {
dispatch(loginFail());
dispatch(reset());
}
I was using this method in other file to handle login
I added {withCredentials: true} in both methods as option and it worked for me.
I am late to the party but nothing fixed it for me. This is what I was missing (and yeah, it's stupid):
I had to add res.send() after res.cookie() - so apperently sending a cookie is not enough to send a response to the browser.
res.cookie("testcookie", "text", cookieOptions);
res.send();
You have to combine:
including credentials on the request with, for example withCredentials: true when using axios.
including credentials on the api with, for example credentials: true when using cors() mw.
including the origin of your request on the api, for example origin: http://localhost:3000 when using cors() mw.
app.post('/api/user/login',(req,res)=>{
User.findOne({'email':req.body.email},(err,user)=>{
if(!user) res.json({message: 'Auth failed, user not found'})
user.comparePassword(req.body.password,(err,isMatch)=>{
if(err) throw err;
if(!isMatch) return res.status(400).json({
message:'Wrong password'
});
user.generateToken((err,user)=>{
if(err) return res.status(400).send(err);
res.cookie('auth',user.token).send('ok')
})
})
})
});
response
res.cookie('auth',user.token).send('ok')
server gives response ok but the cookie is not stored in the browser
Solution :
Add Postman Interceptor Extension to chrome which allows postman to store cookie in browser and get back useing requests.

Resources