I have been struggling on this problem for days. I have a NextJS frontend running on localhost:3000 and an ExpressJS backend running on localhost:3001.
I am trying to build a login flow where I send an axios login request to the backend, authenticate with passport, and send the cookie back to the frontend (that can subsequently be used).
When I run through the flow, I can successfully send data to the backend and authenticate with passport (which writes a row to my session table), and redirect on the frontend. However, I do not see the cookie in my frontend browser (Inspect Element > Application > Cookies > localhost:3000). And when I am redirected to my dashboard page, I show as unauthorized from my status endpoint (which I believe means the cookie is not being set correctly). When I hit the backend endpoint with Postman, I can see the cookie is successfully sent and the subsequent /status endpoint call returns as authorized.
Can anyone help me understand why my cookies aren't being set correctly?
Backend - Express Setup:
const app = express ()
// Enable parsing middleware for requests
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
// Enable CORS
const originURL = process.env.RAILWAY_STATIC_FRONTEND_URL ? process.env.RAILWAY_STATIC_FRONTEND_URL : process.env.LOCAL_STATIC_FRONTEND_URL || 'http://localhost:3000'
app.use(cors({
origin: [originURL],
credentials: true
}))
// Session store
const pgSession = require('connect-pg-simple')(session);
const postgreStore = new pgSession({
// check interface PGStoreOptions for more info https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/connect-pg-simple/index.d.ts
// pool: poolInstance,
createTableIfMissing: true, // this will create a `session` table if you do not have it yet
})
// 1000ms * 60seconds * 60min * 24hrs * 7days = 1 week
const maxAge = 1000 * 60 * 60 * 24 * 7
app.use(session({
secret: process.env.EXPRESS_SESSION_SECRET || 'secret',
resave: false,
saveUninitialized: false,
cookie: {
maxAge: maxAge,
sameSite: "none",
secure: false,
httpOnly: false
},
store: postgreStore,
}))
// Enable Passport
app.use(passport.initialize())
app.use(passport.session())
// Prefix all backend routes with '/api'
app.use('/api', routes)
Backend - Login + Status Routes:
import passport from 'passport';
import bcrypt from 'bcryptjs';
import { PrismaClient } from '#prisma/client';
import { Router } from "express";
const router = Router ();
const prisma = new PrismaClient();
router.post('/login', passport.authenticate('local'), (req, res) => {
return res.sendStatus(200)
})
router.get('/status', (req, res) => {
return req.user
? res.send(req.user)
: res.status(401).send({ msg: "Unauthorized" })
})
export default router
Frontend: Login API Call
import type { NextApiRequest, NextApiResponse } from 'next'
import axios from "axios"
type ResponseData = {
message: string
}
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const BACKEND_API_URL = process.env.RAILWAY_STATIC_BACKEND_URL ? process.env.RAILWAY_STATIC_BACKEND_URL : process.env.NEXT_PUBLIC_LOCAL_STATIC_BACKEND_URL
const headers = {
}
const inputs = {
username: req.body.username,
password: req.body.password
}
if (!headers) return res.redirect(302, '/')
const config = {
headers: headers,
withCredentials: true
}
try {
// const { data } = await axios.post(`${BACKEND_API_URL}/api/auth/login`, inputs, { withCredentials: true })
await axios.post(`${BACKEND_API_URL}/api/auth/login`, inputs, { withCredentials: true })
return res.redirect(307, '/dashboard')
} catch (err) {
console.log(err)
return res.redirect(302, '/login')
}
}
Frontend - Dashboard Page
import { GetServerSidePropsContext, NextPage } from 'next'
import axios from 'axios'
type PageProps = {
msg: String,
}
const DashboardPage: NextPage<PageProps> = ({ msg }) => {
console.log(msg)
return (
// <div className={styles.container}>
<div>
<div> Dashboard Page </div>
<div> { msg } </div>
</div>
)
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
const BACKEND_API_URL = process.env.RAILWAY_STATIC_BACKEND_URL ? process.env.RAILWAY_STATIC_BACKEND_URL : process.env.NEXT_PUBLIC_LOCAL_STATIC_BACKEND_URL
const headers = {
}
const config = {
headers: headers,
withCredentials: true
}
let msg
try {
const user = await axios.get(`${BACKEND_API_URL}/api/auth/status`, { withCredentials: true })
console.log(user)
msg = user
} catch (err) {
// console.log(err)
console.log(err.response.data)
msg = err.response.data.msg
}
var response = {
props: {
msg
}
}
return response
}
export default DashboardPage
It is basically because the client side is not storing the cookie. It is an issue from backend. There will be different settings while running the project in local and in cloud.
Try tweaking like this in your session settings and check in local environment. These worked for my local environment.
{ httpOnly: true, sameSite: 'None', secure: true, }
you have to tweak this in trial and error method for cloud hosting in railway.
Related
I am trying to send an axios request from the vue front end to node js backend with sessionID to handle sessions for admins.
this is a login sample to login admins
axios.defaults.withCredentials = true;
const submit = async (data) => {
const { email, password } = data;
const url = "http://localhost:5000/api/v1/users/loginAdmin";
try {
const res = await axios.post(url, {
email,
password,
});
error.value = undefined;
console.log(res.data);
const loginIng = useLogin();
loginIng.logIn(res.data);
router.push("/");
} catch (err) {
console.log(err);
error.value = err.response.data.message;
}
};
after the login is successfully done and that what happened with me, the user is pushed to the root at / and beforeMount the component, there a check to validate the admin credintials in the backend, like this
axios.defaults.withCredentials = true;
setup() {
const login = useLogin();
const router = useRouter();
onBeforeMount(() => {
axios
.post("http://localhost:5000/api/v1/users/validateAdmin")
.then((res) => {
login.logIn(res.data);
})
.catch((err) => {
console.log(err);
router.push("/login");
});
});
return {};
},
In the backend in the app.js, there is a session and cors policy to allow origin from the front end, and session is stored in sqlite3 like the following.
app.use(
session({
store: new KnexSessionStore({
knex: db,
tablename: "sessions",
}),
secret: process.env.SECRET,
resave: false,
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24,
secure: process.env.NODE_ENV === "production",
},
})
);
in the login admin the session is saving a user with his credintials
like this
const loginAdmin = async (req, res) => {
req.session.user = admin;
}
and this is the ValidateAdmin endpoint sample
const validateAdmin = async (req, res) => {
const userSession = req.session;
const user = userSession.user;
}
the data is being saved to the sqlite3 file in the table, but each time the user visits the endpoint, there is a new id generated for him while i do not need that to happen
What I have already tried:
setting rolling: false, in the session
adding
app.use((req, res, next) => {
req.session.touch();
next();
});
to the app.js
Thanks in advance
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}`))
I'm trying to create an authentification system using firebase, cloud function and express
I've follow this guide for my express app mixed with the Google Docs about cookies.
Here's my server code
index.ts
import { cors } from "./middlewares/cors";
import { initFirebase } from "./utils/firebase";
import { IRoutes } from "./interfaces";
import { routes } from "./routes";
import * as cookieParser from "cookie-parser";
import * as bodyParser from "body-parser";
import * as Express from "express";
import * as functions from "firebase-functions";
// firebase initialize
initFirebase();
// REST API routes
routes.forEach((routerObj: IRoutes) => {
const app = Express();
app.use(cookieParser());
app.use(cors);
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended: true
})
);
// export routes individually for cloud functions
app.use(routerObj.router);
exports[routerObj.name] = functions.region("europe-west1").https.onRequest(app);
});
cors.ts
import * as Cors from "cors";
const options: Cors.CorsOptions = {
credentials: true,
methods: "GET,OPTIONS,POST,DELETE,HEAD,PATCH",
preflightContinue: false,
origin: "*"
};
export const cors = Cors(options);
my api route for login
router.post("/login", async (req: Request, res: Response) => {
const { idToken } = JSON.parse(req.body).data;
// // Guard against CSRF attacks.
// if (csrfToken !== req.cookies.csrfToken) {
// res.status(401).send("UNAUTHORIZED REQUEST!");
// return;
// }
// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
try {
const sessionCookie = await admin.auth().createSessionCookie(idToken, { expiresIn });
const options: CookieOptions = {
signed: false,
maxAge: expiresIn,
httpOnly: false,
secure: false
};
res.setHeader("Cache-Control", "private");
res.cookie("__session", sessionCookie, options);
res.status(200).send({ cookies: req.cookies });
} catch (e) {
console.error(e);
res.status(401).send("UNAUTHORIZED REQUEST!");
}
});
my api route to check connexion status
router.post("/status", async (req: Request, res: Response) => {
const sessionCookie = req.cookies.__session || "";
try {
const decodedClaims = await admin.auth().verifySessionCookie(sessionCookie!, true);
console.log("decodedClaims: ", decodedClaims);
res.end(JSON.stringify({ data: { decodedClaims } }));
// res.redirect("/profile");
} catch (e) {
console.error(e);
res.status(401).send("UNAUTHORIZED REQUEST!");
}
and then how I call the api from my client side (http://localhost:3001)
const idToken = await user.getIdToken();
try {
await fetch("http://localhost:5003/test/europe-west1/user/login",
{
method: "POST",
headers: {
ContentType: "application/json",
Accept: "application/json"
},
body: JSON.stringify({
data: {
idToken
}
})
}
);
} catch (e) {
console.error(e);
}
First point, no __session cookie's created with this code. However, the response of the request is
But nothing in the cookies section of the browser and when I try to get it with req.cookies.__session
Nevertheless if I try to fetch http://localhost:5003/test/europe-west1/user/login directly from the same origin, everything work
I suggest the problem come from cross-origin authorizations
I've checked a lot a issues about this
firebase cloud function won't store cookie named other than "__session"
Express - Firebase - Check undefined req.cookie.__session without throwing error
And more, but nothing work
I'm trying to get a test React frontend and Node/Express backend to correctly set session cookies at Heroku.
This frontend/backend work locally.
This frontend/backend work at Heroku in every browser on my development machine.
But on every other machine I have tested (Windows, Ubuntu), I get this Cross-Origin-Request-Blocked error:
Here is the backend code, where I configure CORS origin correctly:
import express from 'express'
import cors from 'cors'
import morgan from 'morgan'
import session from 'express-session';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
dotenv.config();
const app = express();
app.use(morgan("dev"));
app.set('trust proxy', 1);
app.use(cors({
origin: process.env.FRONTEND_ORIGIN,
credentials: true
}));
app.use(cookieParser());
app.use(session({
name: 'testsession',
secret: 'h$lYS$cr§t!',
resave: true,
saveUninitialized: true,
cookie: {
httpOnly: true,
maxAge: 60 * 60 * 24,
sameSite: process.env.NODE_ENV === "production" ? "none" : "lax",
secure: process.env.NODE_ENV === "production"
}
}))
app.get('/', (req, res) => {
let user = req.session.user;
if (!user) {
res.json({ message: `${(new Date()).toISOString()}: nobody is logged in` })
} else {
res.json({ message: `${(new Date()).toISOString()}: ${user} is logged in` })
}
});
app.get('/login', (req, res) => {
req.session.user = "user001"
res.json({
message: `${(new Date()).toISOString()}: ${req.session.user} is now logged in`
})
})
app.get('/logout', (req, res) => {
req.session.destroy();
res.json({ message: `${(new Date()).toISOString()}: user logged out` })
});
const PORT = process.env.PORT || 3011
app.listen(PORT, () => {
console.log(`API listening on http://localhost:${PORT}`);
});
Here is my frontend code:
import { useState } from 'react';
import './App.scss';
function App() {
const [message, setMessage] = useState('click a button');
const backendUrl = process.env.REACT_APP_BACKEND_URL;
const handle_checkuser = async () => {
const requestOptions = {
method: 'GET',
credentials: 'include'
};
const response = await fetch(backendUrl, requestOptions);
const data = await response.json();
setMessage(data.message);
}
const handle_login = async () => {
const requestOptions = {
method: 'GET',
credentials: 'include'
};
const response = await fetch(`${backendUrl}/login`, requestOptions);
const data = await response.json();
setMessage(data.message);
}
const handle_logout = async () => {
const requestOptions = {
method: 'GET',
credentials: 'include'
};
const response = await fetch(`${backendUrl}/logout`, requestOptions);
const data = await response.json();
setMessage(data.message);
}
return (
<div className="App">
<div><button onClick={handle_checkuser}>checkuser</button></div>
<div><button onClick={handle_login}>login</button></div>
<div><button onClick={handle_logout}>logout</button></div>
<div>{message}</div>
</div>
);
}
export default App;
Why would it be getting this CORS error with some machines and not others?
Backend:
https://github.com/edwardtanguay/et-cookietest-backend
https://et-cookietest-backend.herokuapp.com
Frontend:
https://github.com/edwardtanguay/et-cookietest-frontend
https://et-cookietest-frontend.herokuapp.com
ADDENDUM
I also noticed that on every other machine except for my development machine, the HTTP connection is not secure. This seems to be the cause of the cookie-setting problem.
But how can that be? Why would one particular computer receive HTTPS connections from a website and others HTTP?
I'm developing a register/login website which includes all features to make it work in an efficient and secure way using reactJS, NodeJS and Mysql.
Everything was working fine until I used express-session. In fact, when a user logs in, he will be redirected to a home page (obviously a session will be created) but when the user refreshes the page, It is expected to stay on the home page but the behavior I got is losing the session, thus being redirected to login page.
I looked for a fix and I already tried enabling credentials with Axios in the frontEnd and Cors in the backEnd but the problem is persisting.
This is my code:
server.js
const express = require('express');
const app = express();
const mysql = require('mysql2');
const cors = require('cors');
const validator = require('validator');
const {body, validationResult} = require('express-validator');
const session = require('express-session');
const cookieParser = require('cookie-parser');
app.use(express.json());
app.use(cors({
origin: ['http://localhost:3000'],
methods: ['GET', 'POST'],
credentials: true,
}
));
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
app.use(session({
name: 'session',
secret: 'crud',
resave: false,
saveUninitialized: false,
cookie: {
expires: 60 * 30,
sameSite: 'strict',
}
}
app.post('/login', (req, res) => {
const mail = validator.escape(req.body.mail);
const pass = validator.escape(req.body.pass);
const sqlSelect = 'SELECT * FROM login WHERE mail = ? AND pass = ?';
db.query(sqlSelect, [mail, pass], (err, result) => {
if (err) {
console.log(err);
}
if (result.length > 0) {
req.session.user = result;
req.session.loggedIn = true;
console.log(req.session.user);
res.send({message: 'success', session: req.session});
}
else {
res.send({message: 'Wrong combination Email/Password !'});
}
})
});
app.get('/login', (req, res) => {
console.log(req.session.user);
if (req.session.user){
res.send({
session: req.session,
message: 'logged'
});
}
else {
res.send({
message: 'Not logged'
});
}
});
app.js (login page)
Axios.defaults.withCredentials = true;
const onSubmit = () => {
Axios.post('http://localhost:9001/login', {
mail,
pass,
}).then((response) => {
console.log(response.data.message);
if (response.data.message === 'success') {
history.push('/home');
}
else {
setMessage(response.data.message);
}
});
};
home.js
export default function Home() {
const [user, setUser] = useState('');
const history = useHistory();
useEffect(() => {
Axios.get('http://localhost:9001/login', {withCredentials: true}).then((response) => {
console.log(response.data.message);
if (response.data.message === 'logged'){
setUser(response.data.session.user[0].mail);
}
else {
history.push('/');
}
})
//eslint-disable-next-line
}, []);
return (
<div>
<p>{user}</p>
</div>
)
}
I hope someone is able to suggest some fix to this. I know I can use localStorage but I want to use the session instead.