How can I set-cookie on browser of mobile device? - reactjs

I know there is have question like this, although I following so many answer, I still fix my problem. On computer, the cookie work perfectly, but on mobile it does not, I tried with Safari and Google Chrome.
This is server config
async function bootstrap() {
const app = await NestFactory.create(AppModule,{cors:{
origin:isProduction ? process.env.CORS_PROD : process.env.CORS_DEV,
credentials:true,
}
});
app.useGlobalPipes(new ValidationPipe());
app.use(cookieParser());
app.setGlobalPrefix('finance/api')
await app.listen(4444);
}
bootstrap();
And this function to set-cookie to browser . finance/api is prefix and /users/checkAuth is the route for authenticate user.
async setCookie(userId: string, res: Response) {
const refresh_token = await this.signToken(userId, 'refresh');
res.cookie(this.config.get('COOKIE_NAME'), refresh_token, {
httpOnly: true,
secure: this.config.get('NODE_ENV') === 'production',
sameSite: 'lax',
path: 'finance/api/users/checkAuth',
});
}
My client has a domain like https://example.com
My server has a domain like https://example.com/finance/api
Is this cause by cross-site domain or something ?
I tried to set secure option is false, and turn off Prevent Cross-Site Tracking, I'm also set credentials is true in client.
I hope understand what problem's kind happening and How can I fix it.

Related

I get the cookie from backend, but it is not set in frontend why?

I am using expressjs for backend and vitejs for frontend.
here is my code of backend :
app.use(express.json());
app.use(cors({credentials: true, origin: true, withCredentials: true }))
app.use(cookieParser());
db.query("COMMIT", (err) => {
if (err) return res.status(400).json(err);
const token = createToken(data[0].id, null);
const { password, ...other } = data[0];
return res.cookie("access_token", token, { httpOnly: true, sameSite: 'none', secure: true }).status(200).json(other);
});
frontend code:
await axios.post("http://localhost:8000/user/signup", inputs, { withCredentials: true })
I have tried different browser but it still not working.
If you have different frontend and backend servers (with different hostnames, not just different ports) and the HTML/Javascript served by the frontend server wants to make an HTTP request with cookies to the backend server, this cannot work, because:
Cookies from the backend count as "cross-site" in a request made from the frontend server's page. Such cookies are only sent if they have SameSite=None.
Cookies with SameSite=None must also have the Secure attribute and are then only sent over HTTPS connections.
What you could do:
In your development environment, use HTTP, cookies without SameSite or Secure attributes and have frontend and backend servers both on localhost, just on different ports.
In the production environment, use HTTPS for both frontend and backend servers, cookies with SameSite=None; Secure.

Can't access cookies in localhost even if httpOnly is false

as I wrote in the title of the question I can't access cookies in localhost.
I can see them from the developer tools as you can see here
and I can see the them from Express too if I print them, like here.
But, if I try to print the document.cookie an empty string is returned (as well as if I try to use other libraries).
I use React for the frontend and Express for backend.
React is hosted both in localhost at this URL: http://local.example.com/ and in remote at https://www.example.com/.
I can access the APIs at https://www.example.com/api.
I have no problems reading cookies in the remote React app while I can't in the local one.
React Fetch:
fetch(FETCH_URL + "/user/login", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: "include",
body: JSON.stringify(data)
})
.then((response) => {
if(response.status === 200){
return response.json()
}else{
setError("Username or password are wrong")
}
})
.then((data) => {
if(data){ // Do stuff }
})
Express Cookies:
res.cookie("username", username, {
maxAge: 1000 * 60 * 60 * 2,
httpOnly: false,
secure: true,
domain: "",
sameSite: "None"
})
res.cookie("token", token, {
maxAge: 1000 * 60 * 60 * 2,
httpOnly: true,
secure: true,
domain: "",
sameSite: "None"
})
I know that I can't read from React the "token" cookie because it has the httpOnly flag true, but I expect to be able to read the "username" one (as I can with the remote React App).
I thought it could maybe happend because of the secure flag setted on true, but googling it seems that it should not affect the apps on localhost.
I took a look at many differen questions:
credentials-include, setting to an empty string the domain of the cookie, redirecting from local.example.com to 127.0.0.1 (hosts.ics), sameSite and secure attributes, fixing the cors policies of the backend, tried to set the domain to .example.com and the result is that I receive the cookies, they're saved in the browser, as I said, I can see them from the developer tools, as well from the backend, but I can't access them with JavaScript while I should be able to.
I figured it out. I actually needed to set-up HTTPS for the local domain.
I followed
this guide (it is for both windows and mac/linux and it was pretty quick too). Once done, my final npm-run script (for windows) is similar to the following: set HTTPS=true&&set SSL_CRT_FILE={PATH}&&set SSL_KEY_FILE={PATH}&&set HOST=local.example.com&&react-scripts start
Hope it helps :D

Set-Cookie using CloudFront, Lambda#edge and S3 doesn't work on local

I have created a CloudFront CDN that points to a file in S3 and triggers a Lambda#Edge.
For Lambda#Edge, I have followed this article to return the Set-Cookie header
https://stacktonic.com/article/how-to-set-a-persistent-uuid-cookie-using-cloud-front-and-lamda-edge
let cookieStringUser = cookie.serialize(cookieName, String(uuid), {
maxAge: 60 * 60 * 24 * cookieLifetime,
sameSite: 'Lax',
path: '/',
httpOnly: false,
domain: "kubernetes.docker.internal",
secure: false
});
responseHeaders['set-cookie'] = [{
key: 'set-cookie',
value: cookieStringUser
}];
I had to play around with the arguments to be able to set the cookies in the browser. If I use the CloudFront endpoint say cdf.cloudfront.net/1x1.png(1x1.png is a file in S3) directly, the browser is able to set the cookie.
When I integrate it with the front end application using Fetch:
let response = await fetch('https://cdn.cloudfront.net/1x1.png',
{
method: "GET",
credentials: "include"
});
Tried the Axios approach as well:
let response = await axios.get(`https://cdn.cloudfront.net/1x1.png`, {
withCredentials: true,
});
And return the domain kubernetes.docker.internal (To load the cookie in localhost) in the cookie , I am able to see the cookie in the response header but the browser fails to set the cookie. I have tried playing around with the parameters yet, no luck.
I have tried with access-allow-credentials header, access-allow-origin *, specifying the domains to allow origin, but nothing works.
What am I missing?

Cross-Domain Session Cookie (Express API on Heroku + React App on Netlify)

I have a React App making calls to an API in node.js/Express.
Frontend is deployed in Netlify (https), Backend deployed on Heroku (https).
My problem:
Everything working in dev environment (localhost)
In production (Netlify/Heroku), the api calls to register and login seem to work but the session cookie is not stored in the browser. Because of that, any other calls to protected routes in the API fail (because I don't receive the user credentials).
             
Talking is cheap, show me the code....
Backend (Express API):
I'm using passport (local strategy), express-session, cors.
App.js
require('./configs/passport');
// ...
const app = express();
// trust proxy (https://stackoverflow.com/questions/64958647/express-not-sending-cross-domain-cookies)
app.set("trust proxy", 1);
app.use(
session({
secret: process.env.SESSION_SECRET,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax',
maxAge: 60000000,
secure: process.env.NODE_ENV === "production",
},
resave: true,
saveUninitialized: false,
ttl: 60 * 60 * 24 * 30
})
);
app.use(passport.initialize());
app.use(passport.session());
// ...
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
//...
app.use('/api', require('./routes/auth-routes'));
app.use('/api', require('./routes/item-routes'));
CRUD endpoint (ex. item-routes.js):
// Create new item
router.post("/items", (req, res, next) => {
Item.create({
title: req.body.title,
description: req.body.description,
owner: req.user._id // <-- AT THIS POINT, req.user is UNDEFINED
})
.then(
// ...
);
});
Frontend (React App):
Using Axios with the option "withCredentials" set to true...
User registration and login:
class AuthService {
constructor() {
let service = axios.create({
baseURL: process.env.REACT_APP_API_URL,
withCredentials: true
});
this.service = service;
}
signup = (username, password) => {
return this.service.post('/signup', {username, password})
.then(response => response.data)
}
login = (username, password) => {
return this.service.post('/login', {username, password})
.then(response => response.data)
}
//...
}
Creating a new item...:
axios.post(`${process.env.REACT_APP_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
Short answer:
It wasn't working as expected because I was testing on Chrome Incognito and, by default, Chrome blocks third party cookies in Incognito mode (more details).
Below is a list with some things to check if you're having a similar issue ;)
Checklist
In case it helps, here's a checklist with different things that you main need ;)
(Backend) Add "trust proxy" option
If you're deploying on Heroku, add the following line (you can add it before your session settings).
app.set("trust proxy", 1);
(Backend) Check your session settings
In particular, check the option sameSite and secure (more details here).
The code below will set sameSite: 'none' and secure: true in production:
app.use(
session({
secret: process.env.SESSION_SECRET || 'Super Secret (change it)',
resave: true,
saveUninitialized: false,
cookie: {
sameSite: process.env.NODE_ENV === "production" ? 'none' : 'lax', // must be 'none' to enable cross-site delivery
secure: process.env.NODE_ENV === "production", // must be true if sameSite='none'
}
})
);
(Backend) CORS config
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
(Backend) Environment Variables
Setup the environment variables in Heroku. For example:
FRONTEND_APP_URL = https://my-project.netlify.app
IMPORTANT: For the CORS URL, avoid a trailing slash at the end. The following may not work:
FRONTEND_APP_URL = https://my-project.netlify.app/ --> avoid this trailing slash!
(Frontend) Send credentials
Make sure you're sending credentials in your API calls (you need to do that for all calls you make to the API, including the call for user login).
If you're using axios, you can do use withCredentials option. For example:
axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/items`, {
title: this.state.title,
description: this.state.description,
}, {withCredentials:true})
.then( (res) => {
// ...
});
(Browser) Check the configuration for third-party cookies
For testing, you probably want to make sure you're using the default configuration provided by each browser.
For example, as of 2021, Chrome blocks third-party cookies in Incognito mode (but not in "normal" mode), so you probably want to have something like this:
...and deal with browser restrictions...:
Finally, keep in mind that each browser has a different policy for third party cookies and, in general, those restrictions are expected to increase in the coming years.
For example, Chrome is expected to block third-party cookies at some point in 2023 (source).
If your App needs to bypass those restrictions, here are some options:
Implement Backend & Frontend under the same domain
Implement Backend & Frontend under subdomains of the same domain (example, example.com & api.example.com)
Have your Backend API under a proxy (if you're using Netlify, you can easily setup a proxy using a _redirects file)
The issue comes down to third party cookies.
If you're sending data from server.herokuapp.com to site.herokuapp.com you're going to have this issue.
The solution is to use a custom domain for your Heroku applications.
Please see this post for more details: Cookies Only set in Chrome - not set in Safari, Mobile Chrome, or Mobile Safari

Cookie handling in react and expressjs

I'm trying to send cookie from the server to the react app and use this cookie as auth in the middleware. With the postman, everything is working, but in the browser, it is not.
As I have read some tutorials I need to set up cors and here is the snippet of my server-side app.
app.use(cors({ origin: "http://localhost:3000/", credentials: true }));
res.status(200).cookie("login_auth", token, { httpOnly: true, domain: "http://localhost:3000" }).json({ user });
Then I'm sending the post request
axios.post("http://localhost:5000/user/login", { email, password }, { withCredentials: true })
but when I check the cookie storage for my app there is no cookie and further, I have no idea how to send the cookie back to the server to fulfill the middleware. In postman it is all done so easily.
I can save the cookie with the "js-cookie", but I don't think it is the right way to do it.
Cookies.set("login_auth", response.data.token);
Somebody help?

Resources