Some minor information about the problem:
The problem does not occur when ran on localhost/develpoment.
Hosted on App engine with PostgresSQl and I can see that it adds session to Postgres Table in GCP.
I have a very weird problem regarding session. I am trying to implement a login for my web app using Steam-auth on the API. However, it only works to sign in when using Google chrome(not incognito mode). I have tried firefox and safari to but it wont work. The reason is that incognito mode and firefox sends different cookies or less cookies on every request.
I at first thought the problem might have been caused by no session store but after implementing connect-pg-simple it didn't fix it. So from what I can tell it must be a setting issue with the session.
I am sending all requests from with 'withCredentials:true'.
import axios from 'axios';
// config
// ----------------------------------------------------------------------
const HOST_API = process.env.NODE_ENV === "production" ? "https://www.norskins-api.com/api/v1/" : "http://localhost:3005/api/v1/";
const axiosInstance = axios.create({
baseURL: HOST_API,
withCredentials: true
});
axiosInstance.interceptors.response.use(
(response) => response,
(error) => Promise.reject((error.response && error.response.data) || 'Something went wrong')
);
export default axiosInstance;
The site is hosted at norskins.com if you wanna inspect network and see the changes in the cookies.
Server.js:
//Over this are just a lot of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.use(
session({
...sessionSettings, store: new (require('connect-pg-simple')(session))({
pool: pool
}),
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "A SECRET", //this is obv correct
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/", (req, res) => {
res.send("Welcome to the most clever backend of all time");
});
app.get("/api/v1/user", (req, res) => {
console.log(req.session.steamuser)
if (req.session.steamuser) {
res.send(req.session.steamuser)
}
else {
res.send(false)
}
});
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: "/" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.post("/api/v1/logout", (req, res) => {
req.session.destroy();
res.status(200).send();
});
app.listen(port, () => {
console.log("Listening, port " + port);
});
Session Settings:
const rateLimit = require('express-rate-limit');
const isProduction = process.env.NODE_ENV === 'production';
const sessionSettings = {
secret: "ThisSuperSecretKeyThatStackWontSee", //obv something else
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: isProduction, sameSite: isProduction ? "none" : "lax"
},
name: 'Session_Id',
};
const urlSettings = {
FRONTEND_URL: isProduction ? "https://www.norskins.no" : "http://localhost:3000",
BACKEND_URL: isProduction ? "https://www.norskins-api.com" : "http://localhost:3005"
}
const corsOptions = {
origin: [urlSettings.FRONTEND_URL],
credentials: true, //access-control-allow-credentials:true
methods: ['POST', 'PUT', 'GET', 'OPTIONS', 'HEAD']
};
const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: isProduction ? 1000 : 50000, // 5 requests,
});
I have never hosted something on GCP before, but I have no clue why it only works to sing into chrome normal. and why everything else have different session_id on each request.
logon()
async function logon(user) {
const users = await db.query("SELECT * FROM users WHERE id=$1", [user.id]);
if (users.rows.length > 0) {
return;
}
else {
const dateToday = new Date().toISOString().substring(0, 10);
await db.query("INSERT INTO users(id,steam_name,last_updated) values($1,$2,$3) returning *", [user.id, user.displayName, dateToday]);
return;
}
}
Related
Link to Backend repo: https://github.com/abeertech01/session-cookies-express
Link to Frontend repo: https://github.com/abeertech01/session-cookies-vue3
Steps to reproduce:
Download and npm i repos
Run npm run dev for both repos
Navigate to: 127.0.0.1:5173
Click on Submit button, note the console message in the chrome inspector, but the cookie does NOT get saved
I want to set 'connect.sid' cookie of express-session in browser. In frontEnd I am using Vue 3.
I followed exactly everything should be added for the cookie to be saved. Maybe I am missing something. But I can't really figure out exactly what I am missing
here is my frontend code:
const submit = async () => {
try {
const { data } = await axios.post(
"http://localhost:4050/new",
{ name: "Abeer" },
{ withCredentials: true }
)
console.log(data)
} catch (error) {
console.log(error)
}
}
here it's backend code:
const express = require("express")
const session = require("express-session")
const cors = require("cors")
const app = express()
const PORT = process.env.PORT || 4050
app.use(
cors({
origin: "http://127.0.0.1:5173",
credentials: true,
})
)
app.use(express.json())
app.use(
session({
resave: false,
saveUninitialized: false,
secret: "session",
cookie: {
maxAge: 24 * 60 * 60 * 1000,
sameSite: "none",
secure: false,
},
})
)
// save a name as a cookie
app.post("/new", async (req, res) => {
try {
const name = req.body.name
req.session.name = name
res.send({ message: "saves" }).status(201)
} catch (error) {
console.log(error)
}
})
app.get("/name", async (req, res) => {
try {
console.log(req.session.name)
res.send({ message: req.session.name })
} catch (error) {
console.log(error)
}
})
app.listen(PORT, () => console.log(`Server is running on ${PORT}`))
Problem: Everything works fine in development but also in the normal google chrome browser. However when I try incognito and Firefox it sends two different session ids. I cant find a good reason why the session ID changes on the callback URL. I can see that the correct information gets stored, but when a user logs in on firefox the session key is different when the user is on the callback URL than what it is when the user on the home page. The key is however the same everytime I refresh the site but this does not help me as during login the session key is wrong. As I said it works perfect in development.
I have activated withCredentials on the front end.
I am just super confused as it works fine on google chrome but not on anything else.
Here is my code:
Server.js
... //a bunch of imports
app.use(cors(corsOptions));
//SESSION SETUP TOOLS
app.use(cookieParser());
app.use(express.json())
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.json())
//DDOS PROTECTION
app.use(compression())
app.use(helmet())
app.use(limiter);
//SESSION SETTINGS
app.set('trust proxy', true);
app.enable('trust proxy')
const sessionMemStore = () => {
if (isProduction) {
const firestore = new FirestoreStore({
dataset: new Firestore(),
kind: 'express-sessions',
});
return firestore
}
else {
return null
}
};
app.use(
session({
...sessionSettings,
store: sessionMemStore()
})
);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Initiate Strategy
passport.use(
new SteamStrategy(
{
returnURL: BACKEND_URL + "/api/auth/steam/return",
realm: BACKEND_URL,
apiKey: "B14DD3E47A70AC859EE73AB2C656CB34",
},
function (identifier, profile, done) {
process.nextTick(function () {
profile.identifier = identifier;
return done(null, profile);
});
}
)
);
app.use(passport.initialize());
app.use(passport.session());
app.get(
"/api/v1/auth/steam",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
res.send(req.user);
}
);
app.get(
"/api/auth/steam/return",
passport.authenticate("steam", { failureRedirect: FRONTEND_URL + "/tos" }),
function (req, res) {
logon(req.user);
req.session.steamuser = req.user;
res.redirect(FRONTEND_URL);
}
);
app.listen(port, () => {
console.log("Listening, port " + port);
});
sessionsettings.
const sessionSettings = {
secret: "someRandomKey",
saveUninitialized: true,
resave: false,
cookie: {
maxAge: 24 * 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: isProduction ? "none" : "lax"
},
name: "session"
};
I have a website project with frontend at Netlify and backend at Heroku. Currently, I use express session for recording login information. Just now, with about 40 users logging into the website at the same time, I started to be logged out by Heroku much more frequently and have the error message "Application error" displayed at my server site, like this.
I wonder if the phenomenon is caused by the large number of session information stored in my server after all the users log in at once (since I use express session), but honestly, I don't know how to transform from express to cookie session.
Also, I'm aware that there are quotas for the number of queries sent to the database, in this case ClearDB (MySQL) under Heroku, yet I am not banned from reconnecting with the server after logging in again to Heroku, so it may not be the problem.
How can I fix it? Thanks in advance!
This is my code in index.js (with some unrelated methods left out) in my website's server folder:
const express = require('express')
const mysql = require('mysql')
const cors = require('cors')
const session = require('express-session')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const mysqlStore = require('express-mysql-session')(session);
const port = 3010
const app = express()
app.use(express.json())
app.use(cors({
origin: ["https://xxx.netlify.app"],
methods: ["GET", "POST"],
credentials: true
}))
const options = {
host: "xxx.cleardb.net",
port: 3306,
user: "xxx",
password: "xxx",
database: "heroku_xxx",
createDatabaseTable: true,
schema: {
tableName: 'session_tab',
columnNames: {
session_id: 'session_id',
expires: 'expires',
data: 'data'
}
}
}
const sessionStore = new mysqlStore(options);
app.use(cookieParser())
app.use(bodyParser.urlencoded({extended: true}))
app.set('trust proxy', 1)
app.use(session({
key: "userId",
secret: "nosecret",
store: sessionStore,
resave: true,
saveUninitialized: false,
cookie: {
sameSite: "none",
secure: true,
httpOnly: true,
maxAge: 600 * 1000
}
}))
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "https://xxx.netlify.app");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
res.setHeader(
"Access-Control-Allow-Methods",
"GET, POST, PATCH, DELETE, OPTIONS"
);
res.setHeader('content-type', 'application/json');
next();
});
const db = mysql.createPool({
// create an instance of the connection to the mysql database
host: 'xxx.cleardb.net', // specify host name
user: 'xxx', // specify user name
password: 'xxx', // specify password
database: 'heroku_xxx', // specify database name
})
...
app.post('/login', (req, res) => {
const username = req.body.username
const password = req.body.password
console.log("username");
console.log(username);
console.log("password");
console.log(password);
db.query(
'SELECT * FROM user where username = ? AND password = ?',
[username, password],
(err, result) => {
console.log("result");
console.log(result);
if (err) {
res.send({ err: err })
}
if (result.length) {
req.session.user = result;
console.log("req.session.user (post /login)");
console.log(req.session.user);
if(result[0].role == "student") {
let name = result[0].lastname + result[0].firstname;
console.log(name);
req.session.userfullname = name;
console.log("req.session.userfullname");
console.log(req.session.userfullname);
db.query('SELECT * FROM contact where studentname = ?',
[name],
(err, result) => {
console.log("req.session.userteacherusername");
console.log(req.session.userteacherusername);
req.session.userteacherusername = result[0].username;
})
}
let output = req.session.user + req.session.userfullname + req.session.userteacherusername;
res.send(output);
req.session.save();
} else {
res.send({ message: 'Wrong username or password.' });
}
},
)
})
app.get('/login', (req, res) => {
console.log("req.session.user (get /login)");
console.log(req.session.user);
if(req.session.user) {
res.send({isLoggedIn: true, user: req.session.user})
} else {
res.send({isLoggedIn: false})
}
})
...
app.post('/logout', (req, res) => {
req.session.destroy(
function(err){
if(err){
res.send(err)
}else{
res.send("successfully logged out.")
}
}
);
})
...
app.listen(process.env.PORT || port, () => {
console.log('Successfully Running server at ' + port + '.')
});
I'm working in devMode with angularjs and express-session with cors middleware and I run frontend from localhost:4200 and backend from localhost:8080
In login request I set user data in session and then when I call "/api/contacts", the session user data is undefined.
I tried to save session with session.save() but it does not work.
I noticed that between calls sessionID changes.
I searched for hours on google but I have not found any solution.
this is the frontend call to "/api/contacts"
this.http.get(environment.apiUrl + '/api/contacts', {
withCredentials: true,
})
this is part of server.js
app.use(cors({origin: [
"http://localhost:4200"
], credentials: true,
}));
let sess = session({
secret: 'my secret',
resave: false,
saveUninitialized: false,
store: new MemoryStore({
checkPeriod: 60000 * 5 // prune expired entries every 24h
}),
cookie: {
secure: app.get('env') === 'production'?true:false,
maxAge: 60000 * 5 ,
}
})
app.use(sess)
// Initialize the app.
var server = app.listen(process.env.PORT || 8080, function () {
});
const authMiddleware = (req, res, next) => {
// here req.session.user IS undefined
if(req.session && req.session.user) {
next();
} else {
res.status(403).send({
status: 403,
errorMessage: 'You must be logged in.'
});
}
};
app.get("/api/contacts", authMiddleware,(req, res) => {
// some code will run if authMiddleware pass
});
app.post('/api/login', validatePayloadMiddleware, (req, res) => {
if (req.body.username === "xx.xxxx#xxxx.xxx" && req.body.password === "xxxxxxx")
{
let user = {
id: req.sessionID,
username: req.body.username,
firstName: "Fabio",
lastName: "Spadaro",
};
req.session.user = user;
req.session.save((err) => {
console.log(err)
});
return res.status(200).json(user);
}
else
{
let body = {
error: true,
errorMessage: 'Permission denied!'
};
return res.status(403).json(body);
}
});
I am currently using the create-react-app boiler plate and have been attempting to add auth. I am using axios as my promise based HTTP libray with React.js. I have been using node with express, express-session, passport and passport-local on the backend.
Here is my server.js file with some exlusions:
const express = require('express');
const mysql = require('mysql');
const app = express();
const cors = require('cors');
const session = require('express-session');
const passport = require('passport');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const LocalStrategy = require('passport-local').Strategy;
// Express only serves static assets in production
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
}
app.set('port', (process.env.PORT || 3001));
app.use(cors({
credentials: true,
origin: 'http://localhost:3000'
}));
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(session({
secret: 'topsecretpassword',
resave: true,
saveUninitialized: false,
cookie: {
path: '/',
originalMaxAge: 1000 * 60 * 60 * 24,
httpOnly: true,
secure: false
}
}));
app.use(passport.initialize());
app.use(passport.session());
// Setup Database connection
const connection = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'mvy_db'
});
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(user, done) {
connection.query('SELECT * FROM users WHERE id=?', user, function(err, userId) {
if (err) {
res.status(400).json({
error: 'Database Error',
id: userId[0]
});
}
done(err, userId[0]);
});
});
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},
function(email, password, done) {
connection.query('SELECT * FROM users WHERE email=?', email, function(err, user) {
if (err) {
return done(err);
}
if (!user.length) {
return done(null, false, { message: 'Incorrect email.' });
}
if (user[0].password !== password) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user[0]);
});
}
));
app.post('/signin', passport.authenticate('local'), function(req, res) {
console.log(req.session);
return res.send('login success!');
});
function isAuthenticated (req,res,next){
console.log(req.session);
if(req.session.passport.user)
return next();
else
return res.status(401).json({
error: 'User not authenticated'
})
}
app.get('/checkauth', isAuthenticated, function(req,res) {
res.status(200).json({
status: 'User Authenticated!'
});
})
app.get('/signout', function(req,res) {
req.session.destroy();
res.status(200).json({ success: 'successfully signed out' });
})
Using postman (and even on the browser), I am able to successfully login and the following is held in the req.session object :
cookie:
{ path: '/',
_expires: null,
originalMaxAge: 86400000,
httpOnly: true,
secure: false },
passport: { user: 1 } }
my login request using axios:
return axios.post(ROOT_URL + 'signin', {
email: e.target.email.value,
password: e.target.password.value
}).then((response) => {
if (response.status === 200) {
console.log(response);
}
})
My checkAuth request using axios (this is where I get a 500 error returned):
axios.get(ROOT_URL + 'checkauth', { withCredentials: true })
.then((response) => {
if (response.status === 200) {
return true;
} else {
return false;
}
});
The req.session object after checking authentication before the error message, note that the passport object doesn't exist anymore:
Session {
cookie:
{ path: '/',
_expires: null,
originalMaxAge: 86400000,
httpOnly: true,
secure: false } }
This is the error message I get on the console when I attempt to check that the user is authorized:
TypeError: Cannot read property 'user' of undefined
at isAuthenticated (/server.js:94:26)
I've been banging my head for hours, trying to resolve this issue. I thought it might have something to do with CORS, but after hours of playing around with it that doesn't seem to be the case. It's still plausible that it's a CORS issue, but what's really flustering me is that it works full well with Postman but not on my Chrome browser. Any help is appreciated!
Alright, so I found the solution to my problem. It appeared to be an issue with axios and the configuration of my get requests. For some reason, using the structure axios.get(URL) .then(response) doesn't work with the withCredentials property.
Instead, I had to send my request as:
axios(ROOT_URL + 'checkauth', {
method: 'get',
withCredentials: true
})
.then((response) => {
if (response.status === 200) {
return true;
} else {
return false;
}
});
Oh because I forgot that axious doesn’t send credentials by default I had to stick with jwt and completely removed session.
You can define an instance of axious which will allow you to make requests much more simply
const $axios = axios.create({
baseURL: 'https://some-domain.com/api/',
withCredentials: true
});