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
Related
I am creating a react app and I was adding functionality of registering users.
Everything was successful but I am unable to access Passport User property in socket I used the same code given in socket.io example
const session = require("express-session");
const passport = require("passport");
io.use(wrap(session({ secret: "cats" })));
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error("unauthorized"))
}
});
This example works fine if domain is same but when I use CORS I am unable to access the passport property in session.
my react app domain is localhost:3000 and socket server domain is localhost:5000
Assuming that you are using same protocol and same domain but different ports it should still work fine if you setup your client and server with cors flags, e.g
// server-side
const io = new Server(httpServer, {
cors: {
origin: "https://example.com",
allowedHeaders: ["my-custom-header"],
credentials: true
}
});
// client-side
import { io } from "socket.io-client";
const socket = io("https://api.example.com", {
withCredentials: true,
extraHeaders: {
"my-custom-header": "abcd"
}
});
The sample above was taken from socket.io docs: https://socket.io/docs/v4/handling-cors/
However, the above configuration will work only if client/server are sharing the same top level domain and same protocol. e.g. client: https://example.com, server: https://server.example.com
I spent some time to figure out myself why:
client: http://127.0.0.1:3000 does not work with server: https://127.0.0.1:8000, notice the protocol difference.
With cors configurations in place, it works fine if I use http://127.0.0.1:8000 for server.
PS: If you need to use different top domains, be aware of SameSite policy that might be in place for your browser: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
This policy might restrict your cookies to be sent to server.
so... if different protocol or domains, you should make sure that you session cookie has SameSite flag set as 'none', via:
const session = require('express-session');
...
// Session setup
const sessionConfig = {
secret: 'secret', // Session secret
resave: false, //don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
cookie: {
sameSite: 'none',
secure: true,
}
}
const sessionMiddleware = session(sessionConfig);
app.use(sessionMiddleware);
...
io.use(wrap(sessionMiddleware));
both sameSite and secure properties are needed if you are playing with https:// protocol
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
This might seem like a redundant question, but please hear me out first:
I'm working with a React Frontend and a Node Backend. I'm using JWT to deal with user authentication. Right now, I'm having trouble actually working with the JWT and performing the authentication. Here's where I'm stuck:
~ I try setting the token as an http cookie in my backend. If i work with postman, I see the token being set. However, when I use req.cookies.token to try and receive the token cookie to perform validation in the backend, I get an undefined value. Am I supposed to be sending the cookie from the frontend to the backend somehow? I feel like this is the part that I am missing.
Please advise!
SO I can give you an alternative solution to handling session making use of express-session and connect-mongodb-session this has tend to been the popular and somewhat secure solution for server session handling
Firstly you will need the following packages
npm i express-session connect-mongodb-session or yarn add express-session connect-mongodb-session
Now that we have packages that we need to setup our mongoStore and express-session middleware:
//Code in server.js/index.js (Depending on your server entry point)
import expressSession from "express-session";
import MongoDBStore from "connect-mongodb-session";
import cors from "cors";
const mongoStore = MongoDBStore(expressSession);
const store = new mongoStore({
collection: "userSessions",
uri: process.env.mongoURI,
expires: 1000,
});
app.use(
expressSession({
name: "SESS_NAME",
secret: "SESS_SECRET",
store: store,
saveUninitialized: false,
resave: false,
cookie: {
sameSite: false,
secure: process.env.NODE_ENV === "production",
maxAge: 1000,
httpOnly: true,
},
})
);
Now the session middleware is ready but now you have to setup cors to accept your ReactApp so to pass down the cookie and have it set in there by server
//Still you index.js/server.js (Server entry point)
app.use(
cors({
origin: "http://localhost:3000",
methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"],
credentials: true,
})
);
Now our middlewares are all setup now lets look at your login route
router.post('/api/login', (req, res)=>{
//Do all your logic and now below is how you would send down the cooki
//Note that "user" is the retrieved user when you were validating in logic
// So now you want to add user info to cookie so to validate in future
const sessionUser = {
id: user._id,
username: user.username,
email: user.email,
};
//Saving the info req session and this will automatically save in your mongoDB as configured up in sever.js(Server entry point)
request.session.user = sessionUser;
//Now we send down the session cookie to client
response.send(request.session.sessionID);
})
Now our server is ready but now we have to fix how we make request in client so that this flow can work 100%:
Code below: React App where you handling logging in
//So you will have all your form logic and validation and below
//You will have a function that will send request to server
const login = () => {
const data = new FormData();
data.append("username", username);
data.append("password", password);
axios.post("http://localhost:5000/api/user-login", data, {
withCredentials: true, // Now this is was the missing piece in the client side
});
};
Now with all this you have now server sessions cookies as httpOnly
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've been trying to use connect-redis to use express session store with AWS Elasticache.
The redis server at AWS I used is using Encryption in-transit, encryption at-rest and Redis AUTH token.
i am using Passport with local strategy to authenticate users
This is how it looks in app.js when I configure it:
const express = require('express'),
app = express(),
session = require('express-session'),
awsHandler = require('./awsHandler'),
passport = require('passport'),
....
....
awsHandler.retrieveServiceCredentials('session').then(keys => {
let secret = keys.session_key;
let redis_auth = keys.redis_auth;
const redis = require('redis');
const redisClient = redis.createClient({
host: 'master.redis-connect.abcd.efg.cache.amazonaws.com',
port: REDIS_PORT,
auth_pass: redis_auth,
tls: { checkServerIdentity: () => undefined }
});
const redisStore = require('connect-redis')(session);
app.use(session({
secret: secret,
resave: false,
saveUninitialized: false,
store: new redisStore({
client: redisClient
})
}));
});
....
....
app.use(passport.initialize());
app.use(passport.session());
The thing is I try to connect to my website, and I get no req.session or req.user (when before using SQLite with connect-sqlite3 package, I had req.user after logging in).
I noticed nothing gets stored in redis, when I connect to the Redis Server and type KEYS * there are no keys. However, when I try to set a key in the Redis server hardcoded in app.js with:
redisClient.set('key', 'value')
It IS setting the key and value in the server (when typing KEYS * we can see it there).
So I do successfully establish connection to the redis server with the client library, however, it seems something happens there that I don't configure properly so the sessions gets stored in the Redis.
I am on AWS environment (Elastic Beanstalk, Elasticache).
hank you for reading and helping!
Best regards.
I solved it, it was a problem in my code :
the awsHandler.retrieveServiceCredentials is an async function, and called after couple seconds.
In the flow, it was already initializing everything in the app (initializing passport session, defining routes, starting the node server to listen on port, etc...), and because the retrieveServiceCredentials returned later than that, the order was wrong, and it initialized everything in the app before we used app.use(prodSessionMiddleware), so therefore the session not included in the app.
We set an interval every second, and check a boolean if the 'then' was called, if it was called, we clear the interval and continue with our life :)