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?
Related
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 have build a React Redux app. It works fine in localhost in every browser(chrome,edge,firefox). But the Heroku deployed app doesn't works in Edge, Firefox , although it worked in chrome perfectly.
My app doesn't seems to send request and receive response ,because i noticed that the login button spinner keeps on spinning because it waits for response. So i think that no response is received from backend.
Following are the conclusion i made after test in different browser:
it works perfectly in every browser in localhost development mode.
After Deploying , herokuapp works perfectly fine in chrome only.
Herokuapp doesn't work in edge , Firefox.
Same is the issue with other system(Friends PC).
It doesn't work in any browser except chrome in Mobile device
After console logging and banging my head for hours i got the following error in microsoft edge:
Access to XMLHttpRequest at 'https://ecrypt.herokuapp.com/user/login' from origin 'http://ecrypt.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://ecrypt.herokuapp.com' that is not equal to the supplied origin.
2.2c59d01c.chunk.js:2 POST https://ecrypt.herokuapp.com/user/login net::ERR_FAILED
Okay, so i figured out that there is some problem with CORS.
Following is my code
Frontend:
import axios from "axios";
const API = axios.create({
baseURL: "https://ecrypt.herokuapp.com",
// withCredentials: false,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
});
// const API = axios.create({ baseURL: "http://localhost:9000" });
const cloudinaryAPI = axios.create({
baseURL: "https://api.cloudinary.com/v1_1/ecryptimgdb",
});
//register new user
export const registerNewUser = (formData) =>
API.post("/user/register", formData);
//Account Activation through Email
export const activation = (activation_token) =>
API.post("/user/activation", {
data: {
activation_token,
},
});
//Login
export const login = (formData) =>
API.post("/user/login", formData, { withCredentials: true });
//get Token
export const getToken = () =>
API.post("/user/refresh_token", null, { withCredentials: true });
//Logout
export const logout = () => API.get("/user/logout", { withCredentials: true });
//get User
export const getUser = (token) =>
API.get("/user/info", {
headers: { Authorization: `${token}` },
});
//PROFILE SETTINGS__________________________________________________________________________________________
export const editProfile = (token, profileData) =>
API.post(
"/user/updateProfile",
{ profileData },
{
headers: { Authorization: `${token}` },
}
);
//forgot password____
export const forgotPass = (email) =>
API.post("/user/forgotPassword", { email });
//reset password_____
export const resetPass = (token, password) =>
API.post(
"/user/resetPassword",
{ password },
{
headers: { Authorization: `${token}` },
}
);
//change password____
export const changePass = (oldPassword, newPassword, token) =>
API.post(
"/user/changePassword",
{ oldPassword, newPassword },
{
headers: { Authorization: `${token}` },
}
);
BACKEND:
//IMPORTS
require("dotenv").config();
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const cors = require("cors");
const cookiesParser = require("cookie-parser");
const path = require("path");
const app = express();
// app.use(cors({ credentials: true, origin: "http://localhost:3000" }));
app.use(cors({ credentials: true, origin: "https://ecrypt.herokuapp.com" }));
app.use(cookiesParser());
// app.use(bodyParser.json({ limit: "30mb", extended: true }));
// app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const CONNECTION_URL = process.env.MONGODB_URL;
// const CONNECTION_URL = process.env.MONGODB_LOCAL_URL;
const PORT = process.env.PORT || 9000;
//MONGODB CLOUD DATABASE CONNECTION________________________
mongoose
.connect(CONNECTION_URL, {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then(() => console.log("Connected to Database :: MongoDB Cloud"))
.catch((err) => console.log(err.message));
// app.use("/", routesIndex);
app.use("/", require("./routes/index"));
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
}
//SERVER LISTENING
app.listen(PORT, (err) => {
if (err) {
console.log(err.message);
} else {
console.log(`Listening on localhost:${PORT}`);
}
});
NOTE
I am using cookies to store token so i needed withCredentials and Authorization headers.
The following headers should be sent by the server (instead of the front)
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token",
},
Try removing headers from your axios request. I don't think those headers are allowed.
Ok, so i figured it out ,in my case i was setting headers as
{
headers: { Authorization: `${token}` },
}
Instead of setting it like above a slight change just worked for me:
{
headers: { Authorization: `Bearer ${token}` },
}
and at backend side in index.js or server.js whatever your file name is ,use cors middle ware like this:
const cors = require("cors");
app.use(
cors({
origin: ["https://blahblah.herokuapp.com", "http://localhost:****"],
credentials: true,
})
);
Note: credentials true if you want to pass cookies and access them at server side.
In my case i wanted to access the HttpOnly cookies at server side.
I need to send cookie in my browser to the Nodejs backend using fetch method. However, the cookie header/value is not displayed in the backend. I am using cors and cookie-parser npm
React
fetch(baseUrl, {
method: 'get',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: 'Basic ' + secret,
},
credentials: 'include'
}).then((res) => {
// ...
})
Nodejs
app.use(cookieParser());
app.use(cors({
origin: 'http://localhost:3000',
credentials: true,
}));
app.use((req, res, next) => {
console.log(req.headers, req.cookies); // no cookie headers
// some process then return new cookie value
res.cookie('cookieName', 'cookieValue', { maxAge: 900000, httpOnly: true });
return next();
});
However, when I use postman to send a request, I can get the cookie value and set new cookie value. Is anyone know what is the problem?
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)
app.get("/route1", async (req, res) => {
res.header('Access-Control-Allow-Origin', '*');
console.log('TCL: req.session', req.session);
res.send(req.session);
});
app.get("/route2", (req, res) => {
req.session.test = "test";
console.log('TCL: req.session', req.session);
res.send(req.session);
});
route2 sets the session data, my terminal/browser logs it just fine.
If I visit route1 in the browser url bar the data successfully gets displayed both in the browser and terminal
If I try to fetch route2 in a react component I dont get the data I stored in req.session.test and my terminal logs only the cookie part of it like this:
TCL: req.session Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: false,
secure: false } }
config:
app.use(session({
secret: prodkeys.sessionSecret,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: false,
secure: false,
}
}));
in component:
const isSession = await axios.get("http://localhost:5001/route1",
{credentials: 'include', proxy: true});
Try using withCredentials: true instead of credentials: 'include'
I cannot find credentials option in official document.
I'm not 100% which part fixed it, but I installed cors and configured my fetch this way and it solved it:
const isSession = await axios.get("http://localhost:5001/route1",
{credentials: 'include', proxy: true, withCredentials: true});
Index.js:
app.use(cors({
origin: ['*'],
methods: ['GET','POST'],
credentials: true // enable set cookie
}));