admin管理员组文章数量:1122826
What is expected?
- I hit the GET /csrf/token endpoint from my frontend
- A new csrf token is generated as no cookie is set yet
- This csrf token is saved to redis store via connect-redis and connect.sid with sessionId is stored as a cookie on the browser
- I hit POST /login endpoint with username and password that requires CSRF token to be present and this works
What is happening?
- The csrf token is saved to redis store but I don't see a connect.sid cookie on my Firefox/Chrome/Safari browser
- req.session.csrfToken returns undefined even though the value is literally saved inside redis
Backend
- My express backend app.js
require("dotenv-flow").config();
const cors = require("cors");
const http = require("http");
const express = require("express");
const passport = require("passport");
const RedisStore = require("connect-redis").default;
const Redis = require("ioredis");
const expressSession = require("express-session");
const { Strategy: LocalStrategy } = require("passport-local");
const { Server } = require("ws");
const helmet = require("helmet");
const { csrfSync } = require("csrf-sync");
const { generateToken, csrfSynchronisedProtection } = csrfSync();
const client = new Redis({
host: process.env.REDIS_SESSION_HOST,
port: process.env.REDIS_SESSION_PORT,
password: process.env.REDIS_SESSION_PASSWORD,
db: process.env.REDIS_SESSION_DB,
});
const store = new RedisStore({ client });
const loggedInUser = {
userId: 1,
userName: process.env.TEST_USER_EMAIL,
isAdmin: false,
};
const sessionParser = expressSession({
secret: process.env.SESSION_SECRET,
resave: process.env.SESSION_RESAVE === "true",
rolling: process.env.SESSION_ROLLING === "true",
saveUninitialized: process.env.SESSION_SAVE_UNINITIALIZED === "true",
cookie: {
httpOnly: process.env.SESSION_HTTP_ONLY === "true",
// Doesnt work if maxAge is not of type Number
maxAge: +process.env.SESSION_MAX_AGE,
//
//
sameSite: process.env.SESSION_SAME_SITE === "true",
secure: process.env.SESSION_SECURE === "true",
},
store,
});
const app = new express();
app.use(
cors({
origin: "http://localhost:3000",
credentials: true,
})
);
//
app.use(
helmet({
contentSecurityPolicy: {
directives: {
frameAncestors: ["'self'", ";],
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
styleSrc: ["'self'", "https:", "'unsafe-inline'"],
baseUri: ["'self'"],
fontSrc: ["'self'", "https:", "data:"],
imgSrc: [
"'self'",
"data:",
";,
";,
],
},
},
referrerPolicy: {
policy: "same-origin",
},
})
);
passport.serializeUser((user, done) => {
done(null, user.userId);
});
passport.deserializeUser(async (userId, done) => {
done(null, loggedInUser);
});
passport.use(
"local",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
badRequestMessage: "email or password is missing",
},
async (email, password, done) => {
if (
email === process.env.TEST_USER_EMAIL &&
password === process.env.TEST_USER_PASSWORD
) {
return done(null, loggedInUser);
} else {
return done(null, false);
}
}
)
);
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(sessionParser);
app.use(passport.initialize());
app.use(passport.session());
app.get("/user", (req, res) => {
return res.json(req.user);
});
app.get("/csrf/token", (req, res) => {
req.session.test = 'abracadabra';
return res.json({ token: generateToken(req) });
});
app.get("/session/token", (req, res) => {
return res.json({ token: req.session.csrfToken, test: req.session.test });
});
app.post("/login", csrfSynchronisedProtection, (req, res, next) => {
passport.authenticate("local", {}, async (error, user, info) => {
if (error) {
return next(error);
}
if (!user) {
return res.json(false);
}
req.logIn(user, (error) => {
if (error) {
return next(error);
}
return res.json(user);
});
})(req, res, next);
});
app.post("/logout", (req, res, next) => {
req.logout();
req.session.destroy((err) => {
if (err) {
return next(err);
}
req.user = null;
res.clearCookie("connect.sid");
return res.json(true);
});
});
const map = new Map();
const server = http.createServer(app);
const websocketServer = new Server({ noServer: true });
server.on("upgrade", (request, socket, head) => {
sessionParser(request, {}, () => {
console.log(
request.session,
request.user,
request.session.user,
request.headers.cookie
);
websocketServer.handleUpgrade(request, socket, head, function (ws) {
websocketServer.emit("connection", ws, request);
});
});
});
websocketServer.on("connection", function (ws, request) {
const user = request.session.user;
map.set(user, ws);
ws.on("message", function (message) {
//
// Here we can now use session parameters.
//
console.log(`Received message ${message} from user ${user}`);
});
ws.on("close", function () {
map.delete(user);
});
});
server.listen(+process.env.PORT, () =>
console.log(`server listening on ${process.env.PORT}`)
);
- My .env.development file
PORT='8000'
REDIS_SESSION_DB='5'
REDIS_SESSION_HOST='localhost'
REDIS_SESSION_PASSWORD='somepassword'
REDIS_SESSION_PORT='6379'
SESSION_HTTP_ONLY='true'
SESSION_MAX_AGE='86400000'
SESSION_NAME='ch_test'
SESSION_RESAVE='false'
SESSION_ROLLING='false'
SESSION_SAME_SITE='true'
SESSION_SAVE_UNINITIALIZED='false'
SESSION_SECRET='abracadabrafoobarbaz'
SESSION_SECURE='false'
TEST_USER_EMAIL='[email protected]'
TEST_USER_PASSWORD='123456789'
Frontend
- It is a Nuxt 2 project and I ll include only the relevant files here
- The store file below executes on the server side first inside nuxtServerInit and sends a GET /csrf/token store/index.js
export const state = () => ({
csrfToken: null,
redirect: null,
})
export const mutations = {
SET_CSRF_TOKEN(state, csrfToken) {
state.csrfToken = csrfToken
},
SET_REDIRECT(state, redirect) {
state.redirect = redirect
},
}
export const actions = {
async getCsrfToken({ commit }) {
try {
const { data } = await this.$axios.get('/csrf/token')
commit('SET_CSRF_TOKEN', data.token)
console.log('SAVE CSRF TOKEN', data.token)
} catch (error) {
console.error(error)
}
},
async nuxtServerInit({ commit, dispatch }, { $dayjs, req }) {
await dispatch('getCsrfToken')
},
}
- I wrote an axios plugin that uses @nuxtjs/axios to get this csrf token from the vuex store (nuxtServerInit stores it first) and then send it as header plugins/axios.js
export default ({ $axios, store }) => {
// Inject $hello(msg) in Vue, context and store.
$axios.defaults.timeout = 30000
//
$axios.defaults.transitional.clarifyTimeoutError = true
$axios.onRequest((config) => {
const csrfToken = store.state.csrfToken
console.log(config.url, 'ON REQUEST', csrfToken)
if (
csrfToken &&
['get', 'post', 'put', 'delete', 'patch'].includes(config.method)
) {
config.headers['X-CSRF-Token'] = csrfToken
}
return config
})
}
Libraries
- express-session
- connect-redis
- ioredis
- cors
- csrf-sync
- helmet
Download link
- DOWNLOAD THE BACKEND HERE
- DOWNLOAD THE FRONTEND HERE
Questions
- I am overworked and probably stressed out badly and I have somehow messed this up
- Kindly help me understand why connect.sid cookie is not stored on the browser side
UPDATE 1
- If I remove the csrf middleware, everything seems to work really well and connect.sid is created as expected
本文标签:
版权声明:本文标题:express - connect.sid cookie is not being created, req.session generates a new sessionID on every request, something is wrong wi 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1736301810a1931305.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论