Why passport authentication seems to work only in local? - node.js

I've a Node.js backend service and a React frontend. It was working till today when I had again an issue related to the CORS. It works fine in my local env but when I deploy this to App Engine the CORS issue is still there. What's is missing here?
Here my code:
Node.JS Backend Service:
const app = express();
/* MIDDLEWARE USER: set up cors to allow us to accept requests from our client */
app.use(
cors({
origin: process.env.CLIENT_URL || 'http://localhost:3001', // allow to server to accept request from different origin
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true, // allow session cookie from browser to pass through
}),
);
I'm using passport to obtain credentials from Google and pass to the server
/* MIDDLEWARE USE: use Session Middleware */
const MAX_AGE = process.env.MAX_AGE || 60 * 60 * 1000;
const SECRET = process.env.SECRET || 'Our Secret';
const DEFAULT_ENV = process.env.NODE_ENV || 'development';
app.use(session({
cookie: {
maxAge: MAX_AGE,
secure: DEFAULT_ENV === 'production',
// secure: true,
httpOnly: true,
},
secret: SECRET,
resave: false,
saveUninitialized: false,
// store: new FileStore(fileStoreOptions),
store: new FirestoreStore({
dataset: new Firestore({
kind: 'express-sessions',
}),
}),
}));
/* MIDDLEWARE USE: use Passport Middleware */
app.use(passport.initialize());
app.use(passport.session());
Then I use react & redux in my frontend and here the code to obtain credentials from my endpoint.
/* RETRIEVE INFO FROM OAUTH AS SOON USER CLICK ON LOGIN WITH GOOGLE */
export const loginWithGoogle = () => {
return (dispatch) => {
dispatch({type: FETCH_START});
axios.post('/auth/login/oauth/success').then(({data}) => {
// console.log('userSignInFromGoogle: ', data);
if (data) {
const {originalMaxAge} = data.session.cookie;
const expireDate = (new Date()).getTime() + originalMaxAge;
localStorage.setItem('token', JSON.stringify(data.result.accessToken));
localStorage.setItem('token_expires_in', JSON.stringify(expireDate));
axios.defaults.headers.common['Authorization'] = 'Bearer ' +
data.result.accessToken;
dispatch({type: FETCH_SUCCESS});
// dispatch({type: USER_DATA, payload: data.result});
dispatch({type: USER_TOKEN_SET, payload: data.result.accessToken});
} else {
dispatch({type: FETCH_ERROR, payload: data.error});
}
}).catch(function(error) {
dispatch({type: FETCH_ERROR, payload: error.message});
// console.log('Error****:', error.message);
});
};
};
/* FUNCTION TO FETCH DATA FROM THE AUTHENTICATED USER */
export const getAuthenticatedUser = () => {
return (dispatch) => {
dispatch({type: FETCH_START});
isTokenExpired();
axios.post('auth/me',
).then(({data}) => {
// console.log('userSignIn: ', data);
if (data.result) {
dispatch({type: FETCH_SUCCESS});
dispatch({type: USER_DATA, payload: data.result});
} else {
dispatch({type: FETCH_ERROR, payload: data.error});
}
}).catch(function(error) {
dispatch({type: FETCH_ERROR, payload: error.message});
// console.log('Error****:', error.message);
if (error) {
dispatch({type: SIGNOUT_USER_SUCCESS});
localStorage.removeItem('token');
localStorage.removeItem('token_expires_in');
}
});
};
};
Here where I define the endpoint for axios:
import axios from 'axios';
/* TODO: Change In production with this */
export default axios.create({
withCredentials: true,
baseURL: `backend-url`,//YOUR_API_URL HERE
headers: {
'Content-Type': 'application/json',
},
});

After some test to figure out what the problem could be here, I finally tested this code on various browser and then only Chrome showed this issue not passing the token from the backend server to the other server. In the end I modified the code snippet related to the session store, adding the "sameSite" property to the cookie. Chrome, in the latest version, requires this property to be specified, otherwise it blocks cookies from server to server.
/* MIDDLEWARE USE: use Session Middleware */
const MAX_AGE = process.env.MAX_AGE || 60 * 60 * 1000;
const SECRET = process.env.SECRET || 'Our Secret';
const DEFAULT_ENV = process.env.NODE_ENV || 'development';
app.use(session({
cookie: {
maxAge: MAX_AGE,
secure: DEFAULT_ENV === 'production',
httpOnly: true,
/*TODO: Fix for chrome*/
sameSite: 'none',
},
secret: SECRET,
resave: false,
saveUninitialized: false,
// store: new FileStore(fileStoreOptions),
store: new FirestoreStore({
dataset: new Firestore({
kind: 'express-sessions',
}),
}),
}));

Related

Cannot set session cookie from express app into vue 3 application using axios

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}`))

Session-key changes on every request on hosting with GCP

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;
}
}

CSRF with fastify session cookies

I have a fastify session plugin that creates user sessions and manages them in postgres, but i want to make sure that i have all my sessions protected from CSRF. Im looking at the fastify-csrf plugin and im not exactly sure how to properly implement this. Do i need to generate the csrf token only when the session cookie is first generated or on all requests?
session plugin:
const cookie = require('fastify-cookie');
const session = require('fastify-session');
const csrf = require('fastify-csrf');
const pgSession = require('connect-pg-simple')(session);
const fp = require('fastify-plugin');
/**
* #param {import('fastify').FastifyInstance} fastify
*/
const plugin = async (fastify) => {
// All plugin data here is global to fastify.
fastify.register(cookie);
fastify.register(csrf, { sessionPlugin: 'fastify-session' });
fastify.register(session, {
store: new pgSession({
conString: process.env.DATABASE_URL,
tableName: 'user_session', // Defaults to 'session'
}),
secret: process.env.SESSION_SECRET,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV !== 'development',
maxAge: 86400 * 1000, // 1 day expiration time
},
});
<!-- This is from the documentation, should this only be applied to the /login route when the cookie is generated? When do i verify that the cookie has not been tampered with?
fastify.route({
method: 'GET',
path: '/',
handler: async (req, reply) => {
const token = await reply.generateCsrf();
return { token };
},
});
// Add the user object to the session for later use.
fastify.addHook('preHandler', (req, reply, next) => {
if (!req.session) req.session.user = {};
next();
});
};
module.exports = fp(plugin);

Session data are lost using express session

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);
}
});

how do i access shop url and access token from multiple files?

I followed this tutorial https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react and I now have an app base which I want to develop from, however, how do I access the shop url (example.myshopify.com) and the accessToken that has been generated on server.js file from another file.
Is there a way to make shop url (example.myshopify.com) and the accessToken a global variable or send to a database so I can access them on all files? I am new to all this so not sure what I am doing.
Edit:
app.prepare().then(() => {
const server = new Koa();
server.use(session({ secure: true, sameSite: 'none' }, server));
server.keys = [SHOPIFY_API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_themes', 'write_themes'],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set('shopOrigin', shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});
console.log(`${shop}`);
console.log(`${accessToken}`);
ctx.redirect('/');
},
}),
);
server.use(verifyRequest());
server.use(async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
return
});
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});

Resources