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:
Talking is cheap, show me the code....
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(
// ...
);
});
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) => {
// ...
});
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 ;)
In case it helps, here's a checklist with different things that you main need ;)
If you're deploying on Heroku, add the following line (you can add it before your session settings).
app.set("trust proxy", 1);
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'
}
})
);
app.use(
cors({
credentials: true,
origin: [process.env.FRONTEND_APP_URL]
})
);
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!
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) => {
// ...
});
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:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With