i am new to MERN stack and i don't know why it generates a new session every request. Whenever i request new session/reload the app from the client side which is the react app it always prompt new session id in the backend console.
import Express, {
json
} from 'express'
import * as http from 'http'
import cors from 'cors'
import session from 'express-session'
import {
config
} from 'dotenv'
import MongoStore from 'connect-mongo'
import {
createHmac,
createHash,
scryptSync,
randomBytes,
timingSafeEqual
} from 'crypto'
import connection, {
connectToDb
} from './Services/Connection.js'
connectToDb();
const app = Express()
const port = 5000
const server = http.createServer(app);
app.set('trust proxy', 1) // trust first proxy
app.use(json())
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
methods: ['GET', 'POST', 'DELETE', 'PUT'],
}))
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true,
proxy: true,
cookie: {
secure: false,
expires: 1000 * 60 * 60 * 24,
sameSite: 'none',
httpOnly: true
},
store: MongoStore.create({
mongoUrl: process.env.DB_URI,
ttl: 1000 * 60 * 60 * 24,
autoRemove: "native",
}),
maxAge: 1000 * 60 * 60 * 24,
}))
app.post('/api/login', async (req,res)=>{
const user = req.body;
if(user.type=="guest"){
const guest = await connection.db.collection("user").insertOne({
...user,
})
currentSession ={
...req.session,
user:user
}
console.log(guest)
return;
}
const found = await connection.db.collection("user").findOne({
email:user.email
})
const [salt,password] = found.password.split(":");
const buffPass = Buffer.from(password,"hex",64);
const hashPass = scryptSync(user.password,salt,64);
if(found._id!=undefined && timingSafeEqual(buffPass,hashPass)){
req.session.user = found;
req.session.save((err)=>{
if(err){
return res.send(500);
}
res.send(req.session)
})
}else{
res.send("NO USER FOUND")
}
})
server.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
fetch('/api/login',{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
credentials:'same-origin',
body: JSON.stringify(user),
}).then((e)=>{
console.log("SUCCESS");
navigate("/type=client")
}).catch((r)=>{
console.log("FAILD")
})
Here is my fetch from client side as of now it still generates new session but if i try to fetch directly from the backend it is working fine.
I don't know what ive done wrong maybe there is something with my package.json on the client side. i just the "proxy":"http://localhost:5000" to it. Maybe i need something to make it work?
Related
I am writing a Node JS backend application and Vue JS front, in the API I need session for keep authenticate the users
I use this components on backend server:
express (4.18.2)
express-session (1.17.3)
connect-mongo (3.3.8)
MongoDB 4.4.4
This is the boot code:
// import libs ---
import config from 'config'
import bodyParser from 'body-parser'
import { v4 as uuidv4 } from 'uuid'
import routes from './routes' // custom folder
import express from 'express'
import session from 'express-session'
import MongoStore from 'connect-mongo'
// set costant ---
const app = express()
const secret = config.get('secret')
// body parser ---
app.use(bodyParser.json())
// setup session ---
app.use(session({
genid: function (req) { return uuidv4() }, // random id
secret,
store: MongoStore.create({ // connect to MongoDB fon Session storage
mongoUrl: db.extendedURL,
dbName: db.name,
// autoRemove: 'native',
// autoRemoveInterval: 10, // in minutes
ttl: 7 * 24 * 3600 // in seconds
}),
cookie: { // cookies manage
path: '/',
maxAge: 6000000,
httpOnly: false,
secure: false,
sameSite: false
},
stringify: false,
saveUninitialized: true,
resave: false,
rolling: false,
unset: 'destroy'
}))
// set server port (now run at localhost:5000) ---
app.set('port', 5000)
// set route ---
app.use('/', routes)
In ./route folder there are index.js imports perfectly
Here it is:
// import libs ---
import express from 'express'
const router = express.Router()
// routes ---
router.post('/login', function (req, res) {
const { body, session } = req
try {
session.user = body.user
console.log('user', session.user)
req.session.save(function (err) {
if (err) return res.status(400).json({ message: err })
return res.status(200).json({ message: 'You have successfully authenticated' })
})
} catch (err) {
return res.status(400).json({ message: err })
}
})
router.get('/test', function (req, res) {
const { session } = req
try {
console.log('session', session)
return res.status(200).json(session)
} catch (err) {
return res.status(400).json({ message: err })
}
})
export default router
When I try to call localhost:5000/login (post) I get all just fine, but when I call localhost:5000/test (get) I get a new session on the response and on MongoDB, it also happens whit multiple call on localhost:5000/test
Why express-session generate new session on every call? I really don't know, I spend few days in there and now i don't know how to do
EDIT (12/Jan/2023)
I managet to get it to work using this very simple CORS configuration on NodeJS server boot code:
app.use(cors({
origin: 'http://localhost:8080',
credentials: true
}))
and the header withCredentials: true on every simple http call from front
But this work only from localhost:8080
How can I do for call the Node JS server from other location without specify every IP addres?
Thank you
To allow every origin you can do
app.use(cors({
origin: '*',
credentials: true
}))
or
app.use(cors({
credentials: true,
origin: true
})
Source : Why doesn't adding CORS headers to an OPTIONS route allow browsers to access my API?
I have a problem with setting and reading cookies. There is a login mask, a user logs in with an email and password, and if successful, his user ID is saved in a cookie (access_token). If I now call further POST or GETs, it is checked whether the token is still up to date. The problem here is that I get an undefined every time I check. What could be the reason? Thank you very much!
verifyToken.js
import jwt from "jsonwebtoken";
export const verifyToken = (req, res, next) => {
const token = req.cookies.access_token;
console.log("verifyToken: " + token); //undefined
if (!token) {
return res.status(200).send({
error: true,
msg: "Authentication Failed.",
});
}
jwt.verify(token, process.env.JWT, (err, user) => {
if (err) {
return res.status(200).send({
error: true,
msg: "Authentication Failed.",
});
}
req.user = user;
next();
});
};
route arts.js
import {
ChangeArt
} from "../controller/arts.js";
import express from "express";
import { verifyToken } from "../verifyToken.js";
const router = express.Router();
router.put("/ChangeArt/:id",verifyToken, ChangeArt);
Login.js Frontend
res
.cookie("access_token", token, {
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
SameSite: process.env.NODE_ENV === "development" ? false : true,
})
.status(200)
.json(others);
Server
//Middleware
const corsOptions = {
credentials: true,
origin: "http://localhost:3000", //or production URL
};
app.use(cookieParser());
app.use(cors(corsOptions));
I was going through the next-auth documentation but didn't find any mention of connecting to custom configured Redis without the use of Upstash for a persistent session store.
My use case is straightforward. I am using Nginx as a load balancer between multiple nodes for my nextJS application and I would like to persist the session if in case the user logs in and refreshes the page as Nginx switches between nodes.
For e.g My Nginx config
server {
listen 80;
server_name _;
location / {
proxy_pass http://backend;
}
}
upstream backend {
ip_hash;
server <nextjs_app_ip_1>:8000;
server <nextjs_app_ip_2>:8000;
}
As you can see from the example Nginx config, there are multiple upstream server pointers here that require user session persistence.
I am using the credentials provider of next-auth as I have a Django-based auth system already available.
I did see the implementation of the next-auth adapter with Upstash. However, I have my own custom server running with Redis.
I tried connecting to Redis using ioredis which works fine as it is connected. However, I am not sure how can I use Redis here with next-auth to persist session and validate at the same time?
For e.g In express, you have a session store which you can pass your Redis Client with and it should automatically take care of persistence. Is there anything I can do to replicate the same behavior in my case?
For e.g In Express
App.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'secret$%^134',
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // if true only transmit cookie over https
httpOnly: false, // if true prevent client side JS from reading the cookie
maxAge: 1000 * 60 * 10 // session max age in miliseconds
}
}))
My Code:
import CredentialsProvider from "next-auth/providers/credentials";
import {UpstashRedisAdapter} from "#next-auth/upstash-redis-adapter";
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL); //points to my custom redis docker container
export const authOptions = {
providers: [CredentialsProvider({
name: 'auth',
credentials: {
email: {
label: 'email',
type: 'text',
placeholder: 'jsmith#example.com'
},
password: {
label: 'Password',
type: 'password'
}
},
async authorize(credentials, req) {
const payload = {
email: credentials.email,
password: credentials.password
};
const res = await fetch(`my-auth-system-url`, {
method: 'POST',
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
}
});
const user = await res.json();
console.log("user", user);
if (!res.ok) {
throw new Error(user.exception);
}
// If no error and we have user data, return it
if (res.ok && user) {
return user;
}
// Return null if user data could not be retrieved
return null;
}
})],
adapter: UpstashRedisAdapter(redis),
pages: {
signIn: '/login'
},
jwt: {
secret: process.env.SECRET,
encryption: true
},
callbacks: {
jwt: async({token, user}) => {
user && (token.user = user)
return token
},
session: async({session, token}) => {
session.user = token.user
return session
},
async redirect({baseUrl}) {
return `${baseUrl}/`
}
},
session: {
strategy: "jwt",
maxAge: 3000
},
secret: process.env.SECRET,
debug: true
}
export default NextAuth(authOptions)
Thank you so much for the help.
UPDATE: it aparently works on firefox, I was using brave. I guess it's blocking the cookie with the session?? I don't know what to do about that.
I'm building an app with Vue, connecting through Axios to an API made with express.
I'm trying to use express-session to manage login sessions and auth. On my localhost it works great, but when I tried to use it from the site hosted on heroku, it breaks. The middleware that checks whether the session has an usuario property blocks the call.
I'm pretty sure it has to do with https instead of http. I tested it on localhost https with some generated credentials and it broke the same way.
The endpoint for login is quite long, but basically checks if the password you gave it is correct, and if it is, it sets req.session.usuario to an object with some user data. After that, when I check again for the session, usuario is not set.
The CORS middleware:
const cors = require("cors");
const whitelist = ["https://localhost:8080", "https://hosted-client.herokuapp.com"];
const corsOptions = {
credentials: true,
origin: (origin, callback) => {
if (whitelist.includes(origin))
return callback(null, true);
//callback(new Error("CORS error"));
callback(null, true);
},
};
module.exports = cors(corsOptions);
The session middleware:
const Redis = require("ioredis");
const connectRedis = require("connect-redis");
const session = require("express-session");
const RedisStore = connectRedis(session);
const redisClient = new Redis(
process.env.REDIS_PORT,
process.env.REDIS_HOST,
{password: process.env.REDIS_PASSWORD}
);
module.exports = session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SECRET,
saveUninitialized: false,
resave: process.env.STATE_ENV === "production",
proxy: true,
cookie: {
secure: process.env.STATE_ENV === "production",
httpOnly: true,
sameSite: "none",
// maxAge: 1000 * 60 * 30, // 30 minutos
},
});
A simple test auth middleware:
module.exports = function (req, _, next) {
if (!req.session || !req.session.usuario) {
const err = new Error("No se encontró una sesión");
err.statusCode = 401;
next(err);
}
next();
}
The Axios instance on the client:
require("dotenv").config();
import axios from "axios";
export default axios.create({
baseURL: process.env.VUE_APP_API,
headers: {
"Content-type": "application/json",
},
withCredentials: true,
});
I'm not sure if that's enough info, if not let me know.
I'm trying to setup CSRF tokens so that I can do a number of checks before issueing a token to the client to use in future requests.
Taking the guidance from the csurf documentation, I've setup my express route with the following:
const express = require('express');
const router = express.Router({mergeParams: true});
const csurf = require('csurf');
const bodyParser = require('body-parser');
const parseForm = bodyParser.urlencoded({ extended: false });
const ErrorClass = require('../classes/ErrorClass');
const csrfMiddleware = csurf({
cookie: true
});
router.get('/getCsrfToken', csrfMiddleware, async (req, res) => {
try {
// code for origin checks removed for example
return res.json({'csrfToken': req.csrfToken()});
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
router.post('/', [csrfMiddleware, parseForm], async (req, res) => {
try {
// this returns err.code === 'EBADCSRFTOKEN' when sending in React.js but not Postman
} catch (error) {
console.log(error);
return await ErrorClass.handleAsyncError(req, res, error);
}
});
For context, the React.js code is as follows, makePostRequest 100% sends the _csrf token back to express in req.body._csrf
try {
const { data } = await makePostRequest(
CONTACT,
{
email: values.email_address,
name: values.full_name,
message: values.message,
_csrf: csrfToken,
},
{ websiteId }
);
} catch (error) {
handleError(error);
actions.setSubmitting(false);
}
Postman endpoint seems to be sending the same data, after loading the /getCsrfToken endpoint and I manually update the _csrf token.
Is there something I'm not doing correctly? I think it may be to do with Node.js's cookie system.
I think your problem is likely to be related to CORS (your dev tools will probably have sent a warning?).
Here's the simplest working back-end and front-end I could make, based on the documentation:
In Back-End (NodeJS with Express) Server:
In app.js:
var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
const cors = require('cors');
var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })
var app = express()
const corsOptions = {
origin: "http://localhost:3000",
credentials: true,
}
app.use(cors(corsOptions));
app.use(cookieParser())
app.get('/form', csrfProtection, function (req, res) {
res.json({ csrfToken: req.csrfToken() })
})
app.post('/process', parseForm, csrfProtection, function (req, res) {
res.send('data is being processed')
})
module.exports = app;
(make sure you update the corsOptions origin property to whatever your localhost is in React.
In Index.js:
const app = require('./app')
app.set('port', 5000);
app.listen(app.get('port'), () => {
console.log('App running on port', app.get('port'));
});
In React:
Create file "TestCsurf.js" and populate with this code:
import React from 'react'
export default function TestCsurf() {
let domainUrl = `http://localhost:5000`
const [csrfTokenState, setCsrfTokenState] = React.useState('')
const [haveWeReceivedPostResponseState, setHaveWeReceivedPostResponseState] = React.useState("Not yet. No data has been processed.")
async function getCallToForm() {
const url = `/form`;
let fetchGetResponse = await fetch(`${domainUrl}${url}`, {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": localStorage.getItem('xsrf-token'),
},
credentials: "include",
mode: 'cors'
})
let parsedResponse = await fetchGetResponse.json();
setCsrfTokenState(parsedResponse.csrfToken)
}
React.useEffect(() => {
getCallToForm()
}, [])
async function testCsurfClicked() {
const url = `/process`
let fetchPostResponse = await fetch(`${domainUrl}${url}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"xsrf-token": csrfTokenState,
},
credentials: "include",
mode: 'cors',
})
let parsedResponse = await fetchPostResponse.text()
setHaveWeReceivedPostResponseState(parsedResponse)
}
return (
<div>
<button onClick={testCsurfClicked}>Test Csurf Post Call</button>
<p>csrfTokenState is: {csrfTokenState}</p>
<p>Have we succesfully navigates csurf with token?: {JSON.stringify(haveWeReceivedPostResponseState)}</p>
</div>
)
}
Import this into your app.js
import CsurfTutorial from './CsurfTutorial';
function App() {
return (
<CsurfTutorial></CsurfTutorial>
);
}
export default App;
That's the simplest solution I can make based on the CSURF documentations example. It's taken me several days to figure this out. I wish they'd give us a bit more direction!
I made a tutorial video in case it's of any help to anyone: https://youtu.be/N5U7KtxvVto