POST/GET request with Credentials not sending cookies - javascript

First of all, I know there is many duplicates of this question but I have tried them all and it doesn't work. Hence I am starting a new one.
apart from axios I also tried using fetch with the same results
backend server
"express": "^4.18.2",
"cors": "^2.8.5",
const express = require("express");
const cors = require("cors");
const jwt = require("jsonwebtoken")
const cookieParser = require('cookie-parser')
require("dotenv").config();
const allowedOrigins = [
'http://127.0.0.1:5173',
'http://localhost:5173',
];
const headers = (req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin)
res.setHeader('Access-Control-Allow-Methods', 'GET, POST')
res.setHeader('Access-Control-Allow-Credentials', true);
}
next();
}
const corsOptions = {
origin: allowedOrigins,
credentials: true,
optionsSuccessStatus: 200
}
app.use(headers);
app.use(cors(corsOptions))
app.use(express.json())
app.use(cookieParser())
app.post("/foo", (req,res) => {
const token = jwt.sign(
{ 'foo': "qwerty" },
process.env.JWT_SECRET,
{ expiresIn: '1d' }
)
res.cookie(
'jwt',
token,
{ httpOnly: true, secure: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000 }
);
}
app.get("/bar", (req,res) => {
const cookies = req.cookies
console.log(cookies) // getting cookies undefined here no matter what i try.
if (!cookies?.jwt) return res.sendStatus(401)
// do something below
}
frontend client
"react": "^18.2.0",
"axios": "^1.3.2",
import axios from 'axios';
const app = async () => {
const instance = axios.create({
baseURL: "http://127.0.0.1:3001",
withCredentials: true //first way to set credentials
})
instance.defaults.withCredentials = true //second way to set credentials
const foo = await instance.post("/foo")
if (!foo.ok) return
const response = await instance.get("/bar", {
withCredentials: true //third way to set credentials
}
console.log(response) //401
}

When you set a cookie, with secure: true, sameSite: 'None', then you must always use HTTPS, otherwise the cookie will not be included.
Also, secure: true, sameSite: 'None', is required to get cookies to be included in HTTP POST requests across domains.

Changing api calls from 127.0.0.1 to localhost fixed the issue. But I don't know why. Hope someone can explain.

I had a similar issue but when i did res.send() my cookie got set. Try this:
res.cookie('jwt',token,{ httpOnly: true, secure: true, sameSite: 'None', maxAge: 24 * 60 * 60 * 1000})
res.send();
Hope it helps.

Related

Express sessionID changes on every client's request

My problem:
When I go to server adress (so I'm using get method) it is working as I would want it to work, the sessionID doesn't change upon HTTP requests, but when I'm using client's fetch method to get to the server adress, the sessionID always changes and that is defect, what I don't want.
Any ideas why this is happening and how could I fix it?
Code of how my sessions are set up:
const session = require('express-session');
...
app.set("trust proxy", 1);
app.use(
session({
secret: process.env.SESSION_SECRET,
saveUninitialized: true,
resave: false,
cookie: {
secure: false,
sameSite: true,
},
})
);
...
app.get("/lobby/:id", (req, res) => {
console.log(req.sessionID);
req.session.test = 1;
});
Client's request
useEffect(() => {
fetch(getServerAdress() + "/lobby/" + code, {
method: "GET",
})
.then((response) => response.json())
.then((data) => setLoading(false))
.catch(() => setLoadingText("Failed to join the lobby"));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
As Mat J. said, fetch does not send cookies for cross-origin by default, so I had to change it:
fetch(getServerAdress() + "/lobby/" + code, {
method: "GET",
credentials: "include",
}
Also I had to enable credentials and origin for CORS on my server:
const cors = require("cors");
app.use(cors({ credentials: true, origin: true }));

Cookie is not included in request header / Server side cannot read req.cookies

I am learning and applying authentication for my blog website!
I am using express-session to handle logins. Cookie on the browser & server sessions works fine.
However, I am having trouble retrieving cookies on the server-side express app. I tried the following:
With cookie-parser, req.cookies & req.signedCookies both returns [Object: null prototype].
Setting CORS
req.cookie & req.header.cookie returns undefined
I can see a "Cookie" header from my connection in the browser network tab.
My code / settings are as follows:
function auth (req, res, next) {
// Problem: Cannot read browser cookie of HTTP requests.
console.log('Based on browser', req.cookie, req.cookies, req.signedCookies);
next();
}
router.get('/', auth, async (req, res) => { // ... }
Middlewares
app.use(cors({
origin: ['http://localhost:3000'],
credentials: true
}));
app.use(cookieParser()) // Also tried with secret option.
app.use(session({
secret: 'top-secret',
resave: true,
rolling: true,
saveUninitialized: false,
store: store, // this is working
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 14,
httpOnly: true,
secure: process.env.NODE_ENV !== 'Development',
sameSite: process.env.NODE_ENV === 'Development' ? 'lax' : 'none'
}
}))
Thank you in advance :)
Edit 1: My fetch code:
If your using http only you should consider 2 things:
Step1 while request in client side:
you should send request like this:
const req = await fetch("http://localhost:7000/api/auth/login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Credentials": true,
},
body: JSON.stringify({
email: formData.get("email"),
password: formData.get("password"),
}),
});
const data = await req.json();
step 2 in express:
const allowedOrigins = ["http://localhost:8000"];
const corsOptions = {
origin: function (origin, callback) {
if (allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
var msg =
"The CORS policy for this site does not " +
"allow access from the specified Origin.";
callback(new Error(msg), false);
}
},
optionsSuccessStatus: 200,
credentials: true,
};
app.use(cors(corsOptions));
now you can get coockies in express by using req.cookies.nameOfCookiesWhichYouSendThroughCoockieParser
I'm using axios (React) + Express-js on Node-js
In order to get the cookie from the server:
Simply set withCredentials: true in the axios request, you can use this config example:
const config = {
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
};
In order to get this cookie from the client:
You also need to set withCredentials: true in the axios request,
And you need to install cookie-parser library on the server:
npm i cookie-parser
Import this library:
const cookieParser = require("cookie-parser");
And use the cookieParser middleware:
app.use(cookieParser());
And finally, req.cookies should return the list of your cookies.

Have an error with cors, if my cors is enable?

I want to do a request get or post to my firebase backend api
this is my request
const verify = async (address) => {
const params = { address }
const { data } = await client.get('test', { params })
}
and my backend i am using cors
const app = require('express')()
const cors = require('cors')
const options = {
origin: true,
methods: 'GET,POST,PUT,DELETE,OPTIONS,HEAD,PATCH',
preflightContinue: false,
optionsSuccessStatus: 204,
}
app.use(cors(options))
app.get('/test', (req, res) => {
console.log('console.log', req.body)
res.status(200).send('hello world!')
})
module.exports = {
Module: () => app,
}
I don't know what happened, but I am getting an error in cors:
Access to XMLHttpRequest at 'https://...functions.net/test?address=AABB' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I suspect you need to insert the URL, on which your Vue App is running into options.origin
Or does options.origin: true set headers to '"Access-Control-Allow-Origin": *'?
EDIT: First, you need req.query.address in order to access the address query param, that you sent from Vue.
EDIT: Without setting your options to cors() I get the Access-Control-Allow-Origin: * Response Header from Express. Might not want to do this in production, but set origin to some URL that use the API.
const app = require('express')()
const cors = require('cors')
app.use(cors())
app.get('/test', (req, res) => {
console.log('console.log', req.query.address)
res.status(200).send(`The address is ${req.query.address || "not existing"}`)
})
app.listen(3000, () => console.log("Express running..."))
I use this. Find this solution on Stackoverflow :
//#region CORS - Area...
var whitelist = [
"http://127.0.0.1:3000",
"https://127.0.0.1:3000",
"https://localhost:3000",
"http://localhost:3000",
];
var corsOptions = {
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
console.log("Blocked-Origin:", origin);
callback(new Error("Not in CORS-Origin-List"));
}
},
methods: ["POST, DELETE, GET"],
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));
//#endregion CORS - Area...
And this solution is not recommended, but for Test ok...
app.use(
cors({
optionsSuccessStatus: 200,
credentials: true,
origin: "*",
methods: ["GET", "POST", "DELETE"],
})
);
Note: origin: "*", not true

Why does Express Session variable (userID) not create a cookie in the browser?

I created a simple server with Express Session and Redis. The Redis server is running (I received 'PONG' when I typed 'redis-cli ping'), and I declared the namespace and exported the interface SessionData to allow me to store userID on req.session (using a basic index.d.ts). However, when I make the login request, the variable userID is stored on req.session, but since the cookie is not set to the browser, it is immediately forgotten/erased. It seems like every request produces a new session and the cookie never saves.
App, Redis, and Session Cookie setup:
// ...
const app = express();
const RedisStore = connectRedis(session);
const redis = new Redis();
app.use(
session({
name: 'testcookie',
store: new RedisStore({
client: redis,
disableTouch: true,
port: 6379,
host: 'localhost',
}),
cookie: {
maxAge: 36000000 * 24 * 365,
httpOnly: true,
sameSite: 'lax',
secure: false,
},
saveUninitialized: false,
secret: 'secret',
resave: false,
})
);
// ...
Login mutation:
#Mutation(() => UserResponse)
async login(
#Arg("usernameOrEmail") usernameOrEmail: string,
#Arg("password") password: string,
#Ctx() { req }: MyContext
): Promise<UserResponse> {
// gets user via inputs (everything works here)
// ...
req.session.userID = user.id;
// userID is set to be a number, as is user.id
// logging req.session.userID works perfectly if done right here
return { user };
}
Query to check if logged in:
#Query(() => User, { nullable: true })
async me(#Ctx() { req }: MyContext): Promise<User | undefined> {
// logging req.session.userID shows undefined
return (req.session.userID)
? await User.findOne({ id: req.session.userID })
: undefined;
}
UPDATE (SOLUTION): This was resolved by going into GraphQL's settings and changing the "request.credentials" property to "include."
I am following the same tutorial Fullstack React GraphQL TypeScript Tutorial in Jun 2022. Since 2021, Apollo's GraphQL Playground does not exist- in it's place there is Apollo studio sandbox (https://studio.apollographql.com/sandbox/explorer)
There is no way I could find to set request.credentials to include in the new apollo studio.
After following these threads:
https://community.apollographql.com/t/allow-cookies-to-be-sent-alongside-request/920
and
https://github.com/apollographql/apollo-server/issues/5775
I came to a not-so-great solution, but that works for what I need.
Essentially, in my setup it seems like the session.cookie parameters 'sameSite' and 'secure' need to be different values depending on if you want your front end to add the cookie vs the apollo studio to add the cookie. This is NOT IDEAL - but I could not find any other combinations of parameters that worked for both. So far I've only found mutually exclusive settings.
On my server's index.ts
I use this session setup when I want to set cookie from front-end localhost:3000
app.use(
session({
name: COOKIE_NAME,
store: new RedisStore({ client: redis, disableTouch: true }),
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
httpOnly: true,
sameSite: "lax", // sets cookie from frontend localhost:3000
secure: false, // sets cookie from frontend localhost:3000
},
secret: "shhhhdonttell",
resave: false,
})
);
I actually change the session setup if I want to set the cookie and session userId from the apollo studio
I use this session setup when I want to set cookie from backend-end localhost:4000/graphql
app.use(
session({
name: COOKIE_NAME,
store: new RedisStore({ client: redis, disableTouch: true }),
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
httpOnly: true,
sameSite: "none", // sets cookie from apollo studio
secure: true, // sets cookie from apollo studio
},
secret: "shhhhdonttell",
resave: false,
})
);
Please comment if you know of a way to use the same settings to allow cookies from both front and backend.
For those of you who are following the tutorial and want more details, here are the other important parts of the setup.
This is my entire index.ts file from backend. Note - I am 8.5 hours into the tutorial - so don't worry if you don't recognize some parts.
import "reflect-metadata";
import { COOKIE_NAME, __prod__ } from "./constants";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import ioredis from "ioredis";
import session from "express-session";
import connectRedis from "connect-redis";
import { MyContext } from "./types";
import cors from "cors";
import { getDataSource } from "./data-source";
import { Post } from "./entities/Post";
const PORT = 4000;
const main = async () => {
const dbconn = await getDataSource()
if (typeof dbconn === "boolean") return
console.log('starting migrations')
dbconn.runMigrations()
// await Post.delete({})
// const orm = await MikroORM.init(microConfig);
// orm.getMigrator().up();
console.log('migrations finished')
const app = express();
const RedisStore = connectRedis(session);
const redis = new ioredis();
// const redisClient = new redis({ legacyMode: true });
redis.connect().catch((err) => `RedisClient Connect error: ${err}`);
!__prod__ && app.set("trust proxy", 1);
app.use(
cors({
origin: ["http://localhost:3000", "http://localhost:4000/graphql", "https://studio.apollographql.com",],
credentials: true,
})
);
app.use(
session({
name: COOKIE_NAME,
store: new RedisStore({ client: redis, disableTouch: true }),
saveUninitialized: false,
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
httpOnly: true,
sameSite: "lax",
secure: __prod__,
},
secret: "shhhhdonttell",
resave: false,
})
);
// app.use(
// session({
// name: COOKIE_NAME,
// store: new RedisStore({ client: redis, disableTouch: true }),
// saveUninitialized: false,
// cookie: {
// maxAge: 1000 * 60 * 60 * 24 * 12, //2 weeks
// httpOnly: true,
// // sameSite: "lax", // front end
// // secure: __prod__, //true in production, is can be false for frontend
// sameSite: "none", //csrf //must be nne for apollo sandbox
// secure: true, //true in production, false on localhost // must be true for apollo sandbox even in dev
// },
// secret: "shhhhdonttell",
// resave: false,
// })
// );
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
}),
context: ({ req, res }): MyContext => ({
dbconn,
req,
res,
redis,
}),
});
await apolloServer.start();
apolloServer.applyMiddleware({
app,
cors: false,
// cors: {
// // origin: "http://localhost:3000",
// origin: [
// "http://localhost:3000",
// "https://studio.apollographql.com",
// "http://localhost:4000/graphql",
// ],
// credentials: true,
// },
});
app.listen(PORT, () => {
console.log(`server started on localhost:${PORT}`);
});
};
main().catch((err) => {
console.error(err);
});
Here is my data-source (the new way to make typeORM connection!)
import { Post } from "./entities/Post";
import { Users } from "./entities/Users";
import { DataSource } from "typeorm";
import path from "path"
import "reflect-metadata";
import { Upvote } from "./entities/Upvote";
export async function getDataSource() {
const typeormDataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "nicole",
password: "yourpassword",
database: "bendb2", //your dbname
logging: false,
synchronize: true,
entities: [Post, Users, Upvote],//take out entites you have't made yet.
subscribers: [],
migrations: [path.join(__dirname, "./migrations/*")],
})
let datasource = await typeormDataSource.initialize().catch(err => {
console.error(err)
console.log("Database connection NOT initialized")
return false
})
return datasource
}
in the createUrqlClient.tsx front end file, I have added
export const createUrqlClient = (ssrExchange: any) => ({
url: "http://localhost:4000/graphql",
fetchOptions: {
credentials: "include" as const,
},
exchanges: [...]
Here is a snapshot of the settings needed in apollo studio. To open these settings, click on the settings/gear icon at the top left inside the SANDBOX input.
make sure to add 'x-forwarded-proto' 'https' to the Shared Headers.
The answer form #NicoWheat is partial right (I guess, correct me if I am wrong). It worked when I send the request with apollo studio (with sameSite:"none" and secure: true) but regardless what are the options the cookies still has not been set for me when I do the mutation through localhost:3000.
Edit: I was wrong, after following the option in create urql client in frontend directory, it worked for me, thanks and appreciate it a lot.

Node.js - csurf invalid csrf token

I'm using the npm module csurf for generating a token. First I get the token from the server, which I then use for the /register request. When I'm reproducing the same steps with postman, it seems to work, but unfortunately not in the application. There it always throws the error message that the token is invalid
--- Server side ---
csrfProtection.js
import csrf from 'csurf';
export default csrf({
cookie: true
});
router.js
import csrfProtection from './../../config/csrfProtection'
router.get('/csrfToken', csrfProtection, async (req, res, next) => {
return res.send(req.csrfToken());
});
router.post(
'/register',
validationHelper({ validation: registerUserValidation }),
csrfProtection,
async (req, res, next) => {
return res.send('user registered');
}
);
app.js
const app = express();
app.use(cookieParser());
app.use(
cors()
);
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
--- Client side ---
const token = await this.$axios.$get('/csrfToken')
// token has the value 1KFmMJVz-dspX9TJo8oRInPzgDA1VY28uqQw
await this.$axios.$post(
'/register',
{
name: 'test456',
email: 'test#gmail.com',
password: '123456789'
},
{
headers: {
'csrf-token': token
}
}
)
Someone experienced the same problem? Frontend and backend are hosted on different domains.
Recently fixed a similar issue regarding 403 for csrf token. A new CSRF token is generated for every post request that happens after the get csrf call.
I found that this is a CORS issue. I fixed by adding below code:
import cors from 'cors';
const allowedOrigins = ['http://localhost:3000', 'http://localhost'];
const corsoptions: cors.CorsOptions = {
allowedHeaders: ["Origin", "X-Requested-With", "Cookie", "Content-Type", "Accept", "X-Access-Token", "Authorization"],
credentials: true,
methods: "GET,PATCH,POST,DELETE",
origin: function (origin, callback) {
// allow requests with no origin
// (like mobile apps or curl requests)
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
var msg = 'The CORS policy for this site does not ' +
'allow access from the specified Origin.';
return callback(new Error(msg), false);
}
return callback(null, true);
},
preflightContinue: false,
};
export const handleCors = (router: Router) =>
router.use(cors(corsoptions));
Please refer to cors package https://www.npmjs.com/package/cors"
You need to add it in your app.js below the cookieParser like so:
app.use(cookieParser())
app.use(csrfProtection)
You are successfully sending a CSRF token to the frontend in your /csrfToken but then your app is generating a new token in your /post.
Here is the link to the respective documentation.

Categories