fetch() cannot set cookies received from the server? - javascript

I am using Express.js server. With cookie-parser I have opened this endpoint
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id)
res.send("set cookie ok")
})
When I manually use the browser to http://localhost:5555/s where I have the website running the browser debug console shows that the cookie have been applied.
But when I use fetch API to do the equivalent, it does not set the cookie.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Why?

I have found the solution. The core of this problem being that my button to trigger the fetch is on http://localhost:3000/. The server is on http://localhost:5555/ (I am simulating real environment on my own machine)
The problem is that this fetch call
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'same-origin'
})
}
Without credentials, the browser cannot send or receive cookies via fetch (https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
With credentials as same-origin I can see the cookies coming from the server in the Set-Cookie response header, but nothing is being stored in the browser. One strange thing is that this response always have HttpOnly tagged after the cookie string regardless of my {httpOnly : true/false} settings on the server. In the case of manually using the browser to the page to do GET request, HttpOnly is being respected as usual, and the cookies are set.
So the solution is to set credentials as include to allow cross-origin cookie sending.
async trySetCookie()
{
await fetch("http://localhost:5555/s",{
method: 'GET',
credentials: 'include'
})
}
Also, on the server side you need to allow a particular origin manually with new headers:
app.get("/s", (req,res) => {
res.cookie("bsaSession", req.session.id, {httpOnly:false})
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
res.header('Access-Control-Allow-Credentials','true'
res.send("set")
})
Not doing this results in
XMLHttpRequest cannot load http://localhost:5555/s. Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.
But the cookie will be set regardless of this error. Still nice to include that header to silence the error.
If you are using cors middleware for Express it is even easier. You can just use these options
var corsOptions = {
origin: 'http://localhost:3000',
credentials: true
}
app.use(cors(corsOptions))
And of course credentials: 'include' is still required at the client side.

5argon's solution was great for me otherwise, but I had to set origin in express cors to true. So in backend:
app.use(
cors({
origin: true,
credentials: true,
})
);
And in fetch:
fetch("http://localhost:5555/s", {
method: 'GET',
credentials: 'include'
})

Related

Chrome dont send cookie with allow cors

i try to send POST request from localhost to another domain.
I set for cookie SameSite = None ; Secure
how it require in
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#see_also
Request made to HTTPS server.
Using AXIOS with
{ withCredentials: true }
But in request information i dont see that cookie send
What is problem? What did i miss?
On server side i use this:
app.use(
cors({
credentials: true,
//origin: true,
origin: function (origin, callback) {
callback(null, true);
},
})
);
its give me in response headers:

Express not setting Cookie

I have 2 servers. One hosting a next.js application on localhost:5555 and another hosting an express server for the api on localhost:4444.
The authentication api returns a cookie however this is not being set in the browser running on localhost:5555.
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:5555',
maxAge: 900000,
httpOnly: true,
});
res.status(200).json({
session: jwtSigned,
role: 'default',
});
My cors setup is:
const options: cors.CorsOptions = {
allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'X-Access-Token', 'Authorization'],
credentials: true,
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:5555',
preflightContinue: false,
};
I'd prefer to set the cookie with the api rather than on the via next.js.
I've tried alternating the cors settings but have had no success. The client call uses axios and has withCredentials set.
As mentioned in the other answer, you cannot set cookie on a different domain, as it can be a security risk.
But from your question...
Since the API server is running the authentication logic, the cookie should be set on the API server domain, NOT on client domain
So in your API server, you can change code to
res.cookie('cokkieName', 'bob', {
domain: '127.0.0.1:4444',
maxAge: 900000,
httpOnly: true,
});
or just remove the domain as it defaults to server domain.
res.cookie('cokkieName', 'bob', {
maxAge: 900000,
httpOnly: true,
});
As you have a client running on localhost:5555 and API server running on localhost:4444, it's a cross domain call and you need to pass withCredentials option to axios to pass the cookie. You might not be able to see the cookie in browser, as it can be a security risk, but can echo the cookie value on server console.
axios.get("url",{
withCredentials: true
}).then(res => {
console.log(res);
});
Note that credentials option work only if Access-Control-Allow-Origin header is not a wildcard like *
I have a created a server to mimic the code you posted. The server basically echoes the request cookie back.
It can be accessed through this stackblitz. You can toggle the withCredentials flag to see the difference in response
You can not set a cookie for other domains, Because that would be an enormous security risk.
But if you are looking for a way to share a cookie between your applications, the only way you have is to share a single domain between these applications, either separate them by url prefix or create one of them as a subdomain of other.
domain.com and api.domain.com. This way by specifying domain.com in your cookie both domains have access to it
If you concern about developing environment of yours, you can use Nginx and proxy_pass these two applications in single domain
What you are trying to do is definitely possible, the problem is that cookies are not port specific: Are HTTP cookies port specific? so you should specify a domain name but no port.
If you set a Cookie on its own domain (or if you don't specify it at all), all of the subsequent requests will carry the cookie even if initiated by a different port (only when withCredentials set to true, though). Also, note that, if set, the domain of the Cookie must match exactly the current domain, or parent domain (localhost is considered as different than 127.0.0.1)
Note: doing so shows the following warning in Chrome:
A cookie associated with a cross-site resource at http://example.com/
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.
So, prepare your code for the future web standards!
I had a similar problem with nuxt.js. To set cookies in your browser using express, I suggest using the library Cookies
In my case I set cookies in express like this:
setResponseCookie(req, res, token, strategyName) {
const isHttps = req.protocol === 'https';
let { cookie: { maxAge } } = config;
const cookieSettings = isHttps => ({
maxAge,
httpOnly: true,
secure: isHttps,
});
const cookies = new Cookies(req, res);
cookies.set('auth.strategy', strategyName, cookieSettings(isHttps))
cookies.set('auth.token', token, cookieSettings(isHttps))
}
and then in express route you can add to this like:
let token = sign(user);
this.setResponseCookie(req, res, token, 'local-login')
res.json({ token, status: 'success' });
or
let token = sign(req.user);
this.setResponseCookie(req, res, token, 'google')
res.redirect(config.client.url);
I managed to fix this issue of the server not setting the browser cookies by setting my res.cookie like the following
res.cookie("Test", "If you see this then it works", {httpOnly: false, secure: true, sameSite: "none"}).json({message: "Finished"});
I checked to see if the cookie was set by opening up the chrome dev tools, going to the application, then cookies, then the site URL.
There are a few things I would like to preference about the above code. This method only works on a URL starting with https, this means it will not fix the problem if on a URL like http://localhost:3000. I don't know if it matters if httpOnly is true or false. Also, What is necessary when setting the cookie this way is to pass in the properties secure: true, sameSite: "none". Passing these values into res.cookie was what finial fixed the issue for me. I have not seen this solution posted anywhere and decided I out to let you know.
My code that fetches the data from the API and triggers the server to set the browser cookie looks like this for anyone who is interested.
fetch(domain.com/testSetCookie', {
method: 'get',
mode: "cors",
credentials: 'include' //<-- important
})
.then((res) => {
//console.log(res);
return; //res.json();
})
.then((data) => {
console.log(data);));
})
.catch((err) => {
console.log("an error happeoned");
console.log(err);
});

How do I fetch cookie data with React?

I have a MERN + Passport.js application that is using fetch to make a call to my express API in order to retrieve data stored in a cookie. I have my React frontend routed to localhost:3000, and my backend routed to localhost:3001. My cookies are not being saved, and thus my session is not persisting within my browser. It is not an issue with the express-sessions or passport middleware, as when I use POSTMAN to execute the necessary calls to my API, the cookie is stored and the session persists.
It is only when I attempt to pass this information through/to my front end that things go wrong. I have been stumped for a while and can't seem to find an answer anywhere.
This is the line that I am using to save the cookie:
handleLogin(event) {
event.preventDefault();
fetch("http://localhost:3001/users/login", {
// credentials: 'include',
credentials: 'same-origin',
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
})
// .then( (response) => response.json())
.then( (response )=> {
if(response.message){
alert(response.message);
}
})
Which correctly calls my API, which logs the current session, user data, and cookie.
Upon refreshing and making another request, I lose the cookie (it was never properly stored in the first place I think), and all session data.
This is the get request that I make whenever I navigate to a new page:
componentDidMount(){
var current_user = "";
fetch("http://localhost:3001/users/", {
// credentials: 'include',
credentials: 'same-origin',
method: "get",
headers: {
'Accept':'application/json',
'Content-Type': 'application/json'
}
})
// .then( (response)=> response.json())
.then( (response)=> {
if(response.user){
current_user = response.user;
this.setState({
user: current_user
}), ()=> console.log(response);
}
})
}
In response, I get an undefined user and no other response, and the cookie is never stored in my browser. But again, if I do this in POSTMAN, strictly doing the POST request followed by that GET request, the proper user data is returned and the cookie is shown in POSTMAN as well.
Any idea as to why fetch is not passing the cookie information back to my front end? I have tried both credentials: 'include' and credentials: same-origin.
Thank you!
It seems like the problem, or at least part of it, is your use of same-origin. From Mozilla docs (italics my own):
omit: Never send cookies.
same-origin: Send user credentials (cookies, basic http auth, etc..) if the URL is on the same origin as the calling script. This is the default value.
include: Always send user credentials (cookies, basic http auth, etc..), even for cross-origin calls.
The definition of "same origin" does not include different ports.
If you change same-origin to include, fetch should start managing your cookies correctly.
If not - do you know for sure that the cookie is "never stored in the browser"? With Chrome, you can check chrome://settings/siteData.

How to set cross domain cookie in Koa

I have a Koa and GraphQL API with an authentication resolver which adds sets an auth token to the response cookies.
My API runs on http://localhost:3000 and my front end runs on http://localhost:3001. In order to make this work, I needed to add withCredentials flag to my API request as well as configure my Koa/Cors options with { origin: false, credentials: true }. Without these settings I would get a CORS error which I find confusing because they are both on localhost so technically the same domain...?
There will be multiple front ends using this API and will need to authenticate and get the auth token set in the cookie. When I spoof my domain name as http://app1.dev & http://app2.dev, both calling the API on http://localhost:3000, the auth token gets set in the response cookies but the browser does not store the cookie like it did when everything was localhost.
On my API, I am using #koa/cors for my configuring my cors and then set the following:
app.use(cors({ origin: false, credentials: true }))
On my front end react app, I have a request function which I use for all my GraphQL queries:
const request = query => {
const request = axios.create({
baseURL: apiUrl,
headers: {'X-apikey': apiKey},
withCredentials: true,
});
return request.post('/graphql', { query })
.then(res => res.data)
.catch(console.error)
}
Why are the cookies not getting set in the browser?
EDIT: Updated my axios request to create instance of axios and attempting to set withCredentials. I am still not seeing the credentials param in my request in dev tools though. I suspect this could be the issue?

Make Axios send cookies in its requests automatically

I am sending requests from the client to my Express.js server using Axios.
I set a cookie on the client and I want to read that cookie from all Axios requests without adding them manually to request by hand.
This is my clientside request example:
axios.get(`some api url`).then(response => ...
I tried to access headers or cookies by using these properties in my Express.js server:
req.headers
req.cookies
Neither of them contained any cookies. I am using cookie parser middleware:
app.use(cookieParser())
How do I make Axios send cookies in requests automatically?
Edit:
I set cookie on the client like this:
import cookieClient from 'react-cookie'
...
let cookie = cookieClient.load('cookie-name')
if(cookie === undefined){
axios.get('path/to/my/cookie/api').then(response => {
if(response.status == 200){
cookieClient.save('cookie-name', response.data, {path:'/'})
}
})
}
...
While it's also using Axios, it is not relevant to the question. I simply want to embed cookies into all my requests once a cookie is set.
You can use withCredentials property.
XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request.
axios.get(BASE_URL + '/todos', { withCredentials: true });
Also its possible to force credentials to every Axios requests
axios.defaults.withCredentials = true
Or using credentials for some of the Axios requests as the following code
const instance = axios.create({
withCredentials: true,
baseURL: BASE_URL
})
instance.get('/todos')
TL;DR:
{ withCredentials: true } or axios.defaults.withCredentials = true
From the axios documentation
withCredentials: false, // default
withCredentials indicates whether or not cross-site Access-Control requests should be made using credentials
If you pass { withCredentials: true } with your request it should work.
A better way would be setting withCredentials as true in axios.defaults
axios.defaults.withCredentials = true
It's also important to set the necessary headers in the express response. These are those which worked for me:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', yourExactHostname);
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
I am not familiar with Axios, but as far as I know in javascript and ajax there is an option
withCredentials: true
This will automatically send the cookie to the client-side. As an example, this scenario is also generated with passportjs, which sets a cookie on the server
So I had this exact same issue and lost about 6 hours of my life searching, I had the
withCredentials: true
But the browser still didn't save the cookie until for some weird reason I had the idea to shuffle the configuration setting:
Axios.post(GlobalVariables.API_URL + 'api/login', {
email,
password,
honeyPot
}, {
withCredentials: true,
headers: {'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json'
}});
Seems like you should always send the 'withCredentials' Key first.
You can use withCredentials property to pass cookies in the request.
axios.get(`api_url`, { withCredentials: true })
By setting { withCredentials: true } you may encounter cross origin issue. To solve that
you need to use
expressApp.use(cors({ credentials: true, origin: "http://localhost:8080" }));
Here you can read about withCredentials
What worked for me:
Client Side:
import axios from 'axios';
const url = 'http://127.0.0.1:5000/api/v1';
export default {
login(credentials) {
return axios
.post(`${url}/users/login/`, credentials, {
withCredentials: true,
credentials: 'include',
})
.then((response) => response.data);
},
};
Note: Credentials will be the body of the post request, in this case the user login information (Normally obtained from the login form):
{
"email": "user#email.com",
"password": "userpassword"
}
Server Side:
const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 5000;
app.use(
cors({
origin: [`http://localhost:${port}`, `https://localhost:${port}`],
credentials: 'true',
})
);
Fatih's answer is still valid and great in 2022.
Also axios.defaults.withCredentials = true will do the trick.
It seems passing { withCredentials: true } to individual axios calls is deprecated.
How do I make Axios send cookies in requests automatically?
set axios.defaults.withCredentials = true;
or for some specific request you can use axios.get(url,{withCredentials:true})
this will give CORS error if your 'Access-Control-Allow-Origin' is set to
wildcard(*).
Therefore make sure to specify the url of origin of your request
for ex: if your front-end which makes the request runs on localhost:3000 , then set the response header as
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
also set
res.setHeader('Access-Control-Allow-Credentials',true);
for people still not able to solve it, this answer helped me.
stackoverflow answer: 34558264
TLDR;
one needs to set {withCredentials: true} in both GET request as well the POST request (getting the cookie) for both axios as well as fetch.
Another solution is to use this library:
https://github.com/3846masa/axios-cookiejar-support
which integrates "Tough Cookie" support in to Axios. Note that this approach still requires the withCredentials flag.
After trying for 2 days long and after trying out from the suggestions here this is what worked for me.
express:
cors: cors({ origin: "http:127.0.0.1:3000", credentials: true, })
Cookie : Make sure your cookie has secure: true, sameSite: "None"
Frontend(React)
axios.defaults.withCredentials = true;
(withCredentials : true did not work for me) to the places where you request the cookie as well as to the place where you send the cookie (GET/POST)
Hope this helps others as well.
You are getting the two thinks mixed.
You have "react-cookie" and "axios"
react-cookie => is for handling the cookie on the client side
axios => is for sending ajax requests to the server
With that info, if you want the cookies from the client side to be communicated in the backend side as well, you will need to connect them together.
Note from "react-cookie" Readme:
Isomorphic cookies!
To be able to access user cookies while doing server-rendering, you
can use plugToRequest or setRawCookie.
link to readme
If this is what you need, great.
If not, please comment so I could elaborate more.
For anyone where none of these solutions are working, make sure that your request origin equals your request target, see this github issue.
I short, if you visit your website on 127.0.0.1:8000, then make sure that the requests you send are targeting your server on 127.0.0.1:8001 and not localhost:8001, although it might be the same target theoretically.
This worked for me:
First, I had to make a new instance of axios with a custom config
Then, I used that axios instance to make a post request
See code below:
const ax = axios.create({
baseURL: 'yourbaseUrl',
withCredentials: true,
});
const loginUser = () => { const body ={username:state.values.email, password:state.values.password};
ax.post('/login',body).then(function(response){
return response}).then().catch(error => console.log(error));}
source:
https://www.npmjs.com/package/axios#creating-an-instance
This won't apply to everyone, but I was using a React frontend with Vite and it was serving the localhost as 127.0.0.1:5173, which is what I put as the CORS allowable domain. As soon as I both to localhost everything worked as expected!
// use this while creating axios instance
const API = axios.create({
baseURL: "http://localhost:4000", // API URL
withCredentials: true,
});
// USE THIS MIDDLEWARE in app.js of backend
first, install cors npm i cors
var cors = require("cors"); // This should be at the end of all middlewares
const corsOptions = {
origin: "http://localhost:3000",
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
In my case, the problem was with the cookie, not with Axios; although I was receiving and sending the cookie from / to the same domain / subdomain / host, I was expecting it to work with different resources in different paths - but my coookie was acting like I had set it to a single Path, even though I omitted that attribute. Explicitly setting Path=/; in the cookie solved the issue.
Set the proxy in package.json(Frontend) and restart the server again (problem solved)

Categories