I'm using Nodejs for my server and Reactjs for my client. In my client, I use axios to perform post/get requests to the server.
During development, everything is working perfectly fine, data is fetched and cookies are set properly. However, when I deploy my server and client to Heroku and Netlify respectively, the cookies are suddenly not set.
Here is my server code:
dotenv.config()
const server = express();
server.use(cors({origin: "https://frontendname.com", credentials: true}));
server.use(express.json());
server.use(express.urlencoded({extended:true}))
server.use(cookieParser())
server.use("/", fetchData)
server.listen(process.env.PORT, ()=>console.log(`Server listening on PORT ${process.env.PORT}`))
mongoose.connect(process.env.MONGO_URI, {useNewUrlParser: true, useUnifiedTopology: true}).then( () => {
console.log("connected to mongoose")
}).catch((error) => console.log(error.message))
My server API code
res.status(201) .cookie("accessToken", accessToken, {domain:"frontendwebsite.com", httpOnly: true, sameSite: 'strict', path: '/', expires: new Date(new Date().getTime() + 60 * 1000 * 4)}) .cookie("refreshToken", refreshToken, {domain:"frontendwebsite.com", httpOnly: true, sameSite: 'strict', path: '/', expires: new Date(new Date().getTime() + 60 * 1000 * 96)}) .json({message: "login verified", username: req.body.username}) .send()
My client API code:
axios.defaults.withCredentials = true
export const loginAuth = async (credentials) => {
return await axios.post("https://backendname.herokuapp.com/loginAuth", credentials).then((res) => {
return res
})
}
I have a strong feeling its due to the domain name that is not correct in the "res.cookie". Since in my development when I'm using localhost for both server and client, it works. My client is hosted on Netlify and server is hosted on Heroku. My client has a custom domain name I got from GoDaddy. I used Netlify DNS on that domain name. Any ideas?
Can you inspect the response coming back from your server (with browser Dev Tools)? Is there any Set-Cookie header / value returning, or is it completely missing?
My guess is the cookie header is there, and being set, but with sameSite set to strict it won't send it to your backend / API. So I think you are right about the domain being incorrect, in res.cookie, you could try that with backendname.herokuapp.com. Since really you want the cookie to be transmitting to / from your backend, especially as it is httpOnly, it will never be used by your frontend / client.
Related
I am developping a reactjs front end app with a nodejs/express backend.
Before, react side, I was accessing my nodejs through the address localhost:5000 but since I want to try it from my mobile device, I naively changed localhost to my #ip 192.168.X.X and as the title says, it does not work.
I am sharing an example of what I tried with the login feature since the server is supposed to send back a cookie.
Front end I use axios :
const result = await axios({
method:'post',
url:'http://192.168.X.X:5000/api/user/login',
data:{
"emailLogin":login,
"password":password
},
withCredentials:true,
headers: { crossDomain: true, 'Content-Type': 'application/json' },
});
Backend I tried this :
const corsConfig = {
credentials:true,
origin:true,
}
app.use(cors(corsConfig));
This first configuration send me back a 200 http code but without any cookie.
I also tried this as backend :
const corsConfig = {
credentials:true,
origin:"*",
}
app.use(cors(corsConfig));
and this time, frontend side, I get the well known Access to XMLHttpRequest error.
I have read somewhere on stackoverflow that when origin is set to '*' credential is supposed to be set to false.
Also I would love to have a complete documentation about cors, react and nodejs but to be honest I couldn't find any that fix my problem.
To sum my problem up :
My reactjs frontend that will deployed on several devices with unknown addresses is supposed to send a POST request to a nodejs backend that will send back a cookie.
I went with the same issue. When my backend is myserver.com and frontend is myclient.com
BackEnd Configuration:
enbling CORS with exact origin instead of "*"
app.use(cors({
origin: [
'http://localhost:4200',
'https://dev.myclient.com',
'https://staging.myclient.com',
'https://www.myclient.com',
],
credentials: true
}))
Setting Cookie with SameSite="None" and Backend Enabled with HTTPS.
res.cookie('access_token', token, {
expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year
secure: true, // set to true - samesite none only works with https
httpOnly: true, // backend only
sameSite: 'none'
});
As your frontend and backend is on different domain. You must specify sameSite attribute to 'none'
Your backend must enabled with HTTPS as samSite='none' only works with HTTPS or else your cookie will be blocked by browser. Check samesite attribute in cookie link.
Enable SSL certificate for your backend myserver.com
I love coding on CodeSandbox for client and Repl for server.
I am learning create an auth microservices to handle twitter login recently.
I followed this tutorial
https://www.freecodecamp.org/news/how-to-set-up-twitter-oauth-using-passport-js-and-reactjs-9ffa6f49ef0/
and setup the client on CodeSandbox and Server on Repl
https://codesandbox.io/s/passport-pratice-twitter-p1ql3?file=/src/index.js
_handleSignInClick = () => {
// Authenticate using via passport api in the backend
// Open Twitter login page
// Upon successful login, a cookie session will be stored in the client
//let url = "https://Passport-pratice-twitter.chikarau.repl.co/auth/twitter";
let url = "http://localhost:4000/auth/twitter";
window.open(url, "_self");
};
https://repl.it/#chiKaRau/Passport-pratice-twitter#index.js
app.use(
cookieSession({
name: "session",
keys: [keys.COOKIE_KEY],
maxAge: 24 * 60 * 60 * 100
})
);
// parse cookies
app.use(cookieParser());
// initalize passport
app.use(passport.initialize());
// deserialize cookie from the browser
app.use(passport.session());
// set up cors to allow us to accept requests from our client
app.use(
cors({
//origin: "https://p1ql3.csb.app", // allow to server to accept request from different origin
origin: "http://localhost:3000",
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
credentials: true // allow session cookie from browser to pass through
})
);
If I test them on localhost, they works perfectly (It displays login successfully)
However, testing them on CodeSandBox and Repl won't work because of req.user is undefined. The passport.session supposes to store the user session/cookie into req as req.user, and it will check whether the req.user exist and send a response back to client.
router.get("/login/success", (req, res) => {
if (req.user) {
res.json({
success: true,
message: "user has successfully authenticated",
user: req.user,
cookies: req.cookies
});
}
});
I also tested on both localhost and browser and set the appropriate domain
Client (PC) and Server (PC) - Working
Client (PC) and Server (REPL) - not Working
Client (CodeSandBox) and Server (PC) - not Working
Client (CodeSandBox) and Server (REPL) - not Working
My Question are why the cookie session is not working on online IDE such as CodeSandBox or Repl?
Is there any solution to get around this and run on CodeSandBox or Repl? If deploy both client and server on a server like heroku or digital ocean, would it gonna works as localhost?
Thanks
My attempts at logging in are not getting saved to express session in production. I am saving the session in Mongo Store and the sessions are coming up in MongoAtlas as modified (they way they should appear), but for some reason the server is not recognizing that there is an existing session and is making a new one. When I enable express-session debug, it logs express-session no SID sent, generating session on each request to the server. This makes me think that the session id isn't getting sent with the request and that the problem has something to do with my client and server being on different domains (my client address is https://example.com and my server is on https://app.example.com. I originally had my client on https://www.example.com but changed it thinking that the cookie was getting mistaken for a 3rd party cookie (maybe it still is).
My client is hosted on Firebase Hosting and my Express server is hosted on Google Cloud Run
my express-session settings
app.set('trust proxy', true)
app.use(session({
secret: 'myappisasecret',
resave: false,
saveUninitialized: false,
secure: true,
store: new MongoStore({mongooseConnection: mongoose.connection}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 7, // 1 week
sameSite: 'lax',
secure: true,
domain: 'mysite.com'
},
proxy: true // I think this makes the trust proxy be useless
}))
Below is my coors server stuff. This code is located above the code above, but I don't think it is causing any issues, but think that it might be important to include.
let whitelist = ['https://app.example.com', 'https://www.example.com', 'https://example.web.app', 'https://example.com']
let corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1 || origin === undefined) {
callback(null, true)
} else {
console.log('Request Origin blocked: ', origin)
callback(new Error('Request blocked by CORS'))
}
},
credentials: true
}
app.use(cookieParser('myappisasecret'))
app.use(cors(corsOptions))
Since the server wasn't receiving a session id, I thought that maybe my client wasn't sending one so I added credentials: 'include' to my client request code
const reqHeaders = {
headers: {
"Content-Type": "application/json"
},
credentials: 'include' as any,
method: "GET"
}
fetch('https://app.example.com/u/loggedIn', reqHeaders)
.then(res => etc...
When this request gets submitted expression-session debug logs:
express-session saving z3ndMizKoxivXR0N9LBZYkPhDG65uvF2 and then
express-session split response
This makes me think that as it tries to save my user data to the session, it gets overwritten at the same time with an initial session data. I have set resave: false. But even then I still get express-session no SID sent with every request sent to the server.
Apparently when hosting with Firebase and Cloud Run cookie headers get stripped due to Google's CDN cache behavior.
Here's the documentation that describes that:
https://firebase.google.com/docs/hosting/manage-cache#using_cookies
I have no clue how to implement sessions now. F
I work with app, that already has its own infrastructure. The task is to integrate session-cookie mechanism. I spent a lot of time to understand why cookies doesn’t set on client side.
I. Briefly.
App settings:
Server: NodeJS
Port: 8081
Client: VueJS
Port: 8088
I use module "express-session" to initialize session mechanism on server side and send cookies to client. Client hasn’t set cookies.
II. Details:
Server’s root file is index.js.
I do the following in it:
Plug in express module:
const express = require('express')
Plug in cors module:
const cors = require('cors')
Add cors settings:
app.use(cors({
origin: 'http://localhost:8088',
credentials: true
}))
Then I initialize session in user.js file and receive client’s connects:
Plug in express-session module:
const session = require('express-session')
Plug in routing by express.Router():
const router = express.Router()
Add session settings:
const EIGHT_HOURS = 1000 * 60 * 60 * 2
const {
SESS_NAME = 'sid',
SESS_LIFETIME = EIGHT_HOURS,
SESS_SECRET = 'test',
NODE_ENV = 'development'
} = process.env
const IN_PROD = NODE_ENV === 'production'
Initialize session:
router.use(session({
name: SESS_NAME,
resave: false,
saveUninitialized: false,
secret: SESS_SECRET,
cookie: {
maxAge: SESS_LIFETIME,
sameSite: false,
// Must have HTTPS to work 'secret:true'
secure: IN_PROD
}
}))
Receive client queries by router.post()
App client side consists of a lot of files. Client send data to NodeJS server by Axios module.
I read several articles by this theme and I guess that server side settings, which I made, are enough for work session-cookie mechanism. That means, that problem is on Vue side.
What I made:
I set in all files, where Axios send data to server, parameter withCredentials in true value (withCredentials: true) to pass CORS restrictions. This didn’t help
App in production has other URLs for accessing the production NodeJS server. I set develop NodeJS server URL in all client side files. This didn’t help
Read this article: Vue forum. From this article I understood, that need to solve this problem by axios.interceptors (StackOverFlow forum). I supposed that if this setting set on one of the client’s side pages, may be cookies should work at least on this page. This didn’t help.
Tried to set setting like this:
axios.defaults.withCredentials = true
And that:
axios.interceptors.request.use( function (config) {
console.log('Main interceptor success')
config.withCredentials = true;
return config;
},
function(error) {
// Do something with request error
console.log('Main interceptor error')
return Promise.reject(error);
}
)
This didn’t help
Please, tell me in which direction I should move? Is that right, that on client side on absolutely all pages must be axios.defaults.withCredentials = true setting to initialize cookies mechanism? What details I miss? If I set session-cookies from scratch the mechanism works.
I resolve this issue. I need to look for cookie storage in another browser place:
Chrome server cookie storage
I'm facing an issue trying to implement HTTPS via a proxy (initially NGINX, now AWS ALB) to secure connections to my node server. My login request is being processed just fine, but subsequent requests after login are being marked as isAuthenticated === false, and therefore my node app is returning 401.
I know isAuthenticated === false is being caused by the session cookie not being set in the browser, however I can't seem to figure out why the session cookie isn't being set (or sent?).
I'm configuring express-session as below:
app.use(cookieParser('secret'));
app.set('trust proxy', 1);
// add & configure middleware
app.use(session({
name: 'session',
store: new redisStore(),
secret: 'secret',
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true,
secure: true }
}));
My node app is sitting behind an AWS Application Load Balancer which communicates with the app over HTTP, so I've configured trust proxy to 1.
My passport configuration and local strategy are just below that:
// configure passport.js to use the local strategy
passport.use(new LocalStrategy(
{ usernameField: 'email' },
(email, password, done) => {
mysql.getConnection((err, conn) => {
conn.query('SELECT u.user_id, u.email, u.password, o.uuid FROM user AS u INNER JOIN organization AS o ON o.id = u.org_id WHERE email = ?;', [email], ( err, rows ) => {
if (err) throw err;
if (rows.length !== 1)
return done(null, false, { message: 'User does not exist.\n' });
else if (!bcrypt.compareSync(password, rows[0].password))
return done(null, false, { message: 'Incorrect password.\n' });
else{
return done(null, {user_id: rows[0].user_id, email: email, uuid: rows[0].uuid});
}
});
conn.release();
});
}
));
The request coming from the react client is:
axios.post('https://sub.mydomain.com' + '/api/login', userObj, {withCredentials: true})
.then(res => {
if (res.status === 200) {
initUser(res.data, true);
}
else {
this.setState({errors: {password: 'Incorrect username or password'}})
this.props.history.push('/login');
}
})
.catch((e) => {
this.setState({errors: {password: 'Unable to login'}})
this.props.history.push('/login');
});
When making the request I'm getting returned a 200 status with the user information as I would expect. See the screenshots from PostMan below:
Also these are the response headers in Chrome:
access-control-allow-credentials: true
access-control-allow-origin: https://sub.mydomain.com
content-length: 89
content-type: text/html; charset=utf-8
date: Tue, 12 Mar 2019 07:49:45 GMT
etag: W/"59-T0xi+VpB6A/MLCHMb8Qz3Pq9dwc"
status: 200
vary: Origin
x-powered-by: Express
Somewhere along the line it seems the session cookie is either failing to be sent from the node app, or set in the browser.
I'm running the exact same on my local machine and the cookie is being set in the browser no problem. The only difference between my local and my server is the ALB in front of the node application, and I'm setting secure: false on my local (as I'm not using HTTPS).
FYI - I had initially tried using an NGINX proxy_pass on my server to handle the HTTPS connections and had the same issue. I had tried removing the proxy pass and using the AWS Application Load Balancer after failing to find a solution with NGINX (I thought there may be an issue with my NGINX config). This leads me to believe the issue is with the express-session configuration as its the common denominator here, however I may be missing something else.
FYI 2 - I've tried setting trust proxy to true, 1, 2, 3, etc, Also tried setting secure to false, Also tried setting httpOnly to false - and countless variations of the above, but still I don't seem to be getting the cookie in the browser.
FYI 3 - I tried removing the proxy and reverting back to a direct HTTP connection to the Node application and I'm still not getting the cookie on in my browser (or in PostMan).
Hoping someone here can point out something I've missed in the hopes of getting this resolved.
Thanks in advance!
So after days of investigating, the issue turned out to be that my Redis server had terminated and failed to restart. facepalm
The sessionID wasn't being generated (or stored) as it couldn't connect to the Redis store to save the session information on the server side.
Starting the Redis server solved the issue immediately, however it would be great if express/passport had some verbose logging to notify users of this.
Hope someone else finds this useful!