Can't get cookies from request in NestJS - nestjs

I got a backend perfectly running in NestJs.
Now I need to add some functionality using cookies. So I have installed cookie-parser, imported it to the main file with import * as cookieParser from 'cookie-parser' and put in to the app with app.use(cookieParser()).
In the controller I have imported Request and Response from express:
import { Request, Response } from 'express';
The endpoint looks like this:
#Get('/refresh')
async refreshToken(
#Res({ passthrough: true }) response: Response,
#Req() request: Request)
{
const {refreshToken} = request.cookies // refreshToken is [Object: null prototype] {}
const userData = await this.authService.refresh(refreshToken)
response.cookie('refreshToken', userData.refreshToken, {maxAge: 30 * 24 * 60 * 60 * 1000, httpOnly: true, secure: true})
return userData
}
I can put cookies to response, but the problem is that there is no cookies in request at all - it shows [Object: null prototype] {}
I was following the official setup https://docs.nestjs.com/techniques/cookies
Any suggestions are much much appreciated!

The problem was in CORS
I had this
app.enableCors()
but after I have added cors in app creation like this (with credentials: true), everything works just fine:
const app = await NestFactory.create(AppModule, { cors: {credentials: true, origin: process.env.CLIENT_URL} })

Related

Why Nestjs not setting cookies in my browser?

I'm making an API in Nestjs that is consumed by an application in ReactJs. My problem is in the login route, when I use swagger, the cookie is saved in the browser normally but when I do a fetch from the front end, the cookie is not saved even though the response headers have the cookie.
I already tried to use all the sameSite options, I tried to put credentials include in the fetch but nothing works. If I log in to swagger first, then I try to do it in react, react copies the cookie that is saved in swagger.
For example, if in swagger I log in with user 1, and in react with the user 2, react steals the cookie from user 1 and ignores user 2 response cookie.
Code in react:
const res = await fetch(`${API_URL}/auth/login`, {
method: "POST",
headers: { "Content-type": "application/json", accept: "*/*" },
// credentials: "include",
body: JSON.stringify(data),
});
Main.ts:
const corsOptions = {
origin:
process.env.NODE_ENV === 'development' ||
process.env.MY_NODE_ENV === 'development'
? [process.env.PLATFORM_LOCAL_URL, process.env.LANDING_LOCAL_URL]
: [process.env.PLATFORM_PROD_URL, process.env.LANDING_PROD_URL],
credentials: true,
allowedHeaders: 'Content-Type, Accept, Origin',
preflightContinue: false,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
};
app.enableCors(corsOptions);
app.use(helmet());
app.use(cookieParser());
Login Controller:
#UseGuards(LocalAuthGuard)
#Post('auth/login')
async login(
#Body() _: MakeAuthDto,
#Request() req,
#Res({ passthrough: true }) res,
) {
const access_token = await this.authService.login(req.user);
const cookiesOpts = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'none',
path: '/',
maxAge: 60 * 60 * 24 * 3,
};
res.cookie('jwt', access_token, cookiesOpts);
return {
response: {
user: req.user,
expire: new Date().setDate(new Date().getDate() + 3),
},
};
}
Work on swagger:
After make request from ReactJs, the response cookies has the jwt:
But the cookie are not stored:
Looks like you're trying to set a cookie with the swagger editor.
See Note for Swagger UI and Swagger Editor users:
Cookie authentication is currently not supported for "try it out" requests due to browser security restrictions. See this issue for more information. SwaggerHub does not have this limitation.

Nextjs not reading cookie from Express response all in localhost

In my local environment, I'm trying to set up user tokens after login in my NextJS app (localhost:3005), using the response from my express backend (localhost:3020). I can see set-cookie in the response on the server, but the cookies in the getServerSideProps is empty always.
My code is pretty basic:
Express backend
// Cors set up
cors({
methods: "GET,HEAD,OPTIONS,POST",
preflightContinue: false,
credentials: true,
origin: [
"http://localhost:3005",
...
],
optionsSuccessStatus: 204,
})
// Response - can see this cookie in set-cookie
return res.cookie("test", "test1", {
httpOnly: true,
sameSite: "none",
expires,
domain: "localhost:3005", // tried without this also
}).redirect("http://localhost:3005/login");
My NextJS app has the following:
//Login component : on login submit
const resp = await fetch(
`${BACKEND_URL}login?originalUrl=/login`, // Redirecting back
{
headers: new Headers({
Authorization: "Bearer " + someToken,
}),
credentials: "include",
method: "POST",
redirect: "follow", // Tried this but did not work
}
);
// resp.redirected is true
// Login component - triggers correctly but no cookies
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const { req } = ctx;
const { cookies } = req;
console.log("cookies", cookies ); // Always {}
return { props: {} };
};
I have been stuck with this for a while now guys, it seems like I'm missing something very trivial here. I'm not super familiar with NextJs, so any help here would be greatly appreciated. Thanks!
This was happening because of the Chrome SameSite=None + Secure requirement. Setting secure to true in the cookie resolved this issue. I did not have to set up SSL certificates for my local environment, just setting secure worked.

Cors Origin specified, but wildcard is sent as response - NodeJS Express Cors()

I have a simple but annoying issue. I am running a nodejs server with Apollo Server for GraphQL and express for handling the web requests. I am setting a cookie after a successful login in redis via express-session
I do set the origin and the credentials options for cors()
However, on the front-end I still get the error message, that there is a wildcard in use.
If I changed the origin to "http://localhost:3000/" it would throw me the message, that it is not included in the origin list.
The cookie gets set:
The response also gives me:
When setting the credentials of createHTTPLink to same-origin no cookie is set. as per documentation (https://www.apollographql.com/docs/react/networking/authentication/)
You just need to pass the credentials option. e.g. credentials: 'same-origin' as shown below, if your backend server is the same domain or else credentials: 'include' if your backend is a different domain.
Error Message:
Access to fetch at 'http://localhost:4000/graphql' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
Node JS index.ts
import "reflect-metadata";
import dotenv from "dotenv";
import { ApolloServer } from "apollo-server-express";
import Express from "express";
import { buildSchema } from "type-graphql";
import session from "express-session";
import connectRedis from "connect-redis";
import {redis} from "./services/redis"
import cors from "cors"
import { createConnection } from "typeorm";
// load .env file
dotenv.config()
const main = async () => {
await createConnection({
name: "default",
type: "postgres",
host: "localhost",
port: 5432,
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
database: process.env.TYPEORM_DATABASE,
synchronize: true,
logging: true,
entities: [__dirname + "/modules/*/*.*"],
});
// build the graphQL schema
// load all the resolvers!
const schema = await buildSchema({
resolvers: [__dirname + "/modules/**/!(*.test).ts"],
});
// create the apollo server with the schema and make sure we have access
// to req and res in context!
const apolloServer = new ApolloServer({
schema,
context: ({req, res} : any) => ({req, res})
});
// initialise Express itself
const app = Express();
// add the cors for the react frontend
app.use(cors({
credentials: true,
origin: "http://localhost:3000"
}))
// create the redis connection
const RedisStore = connectRedis(session)
// setup the redis session
const redisSessionStore = session({
store: new RedisStore({
client: redis as any,
}),
name: process.env.COOKIE_NAME,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 1000 * 60 * 60 * 24 * 1 * 365, // 1 year
},
} as any)
// make sure redis is used before we mix with apollo
app.use(redisSessionStore);
apolloServer.applyMiddleware({ app });
// start the server
app.listen(process.env.PORT, () => {
console.log(`Started on http://localhost:${process.env.PORT}/graphql`);
});
};
main();
React JS Front-End
import { ApolloClient, createHttpLink, InMemoryCache } from "#apollo/client";
const link = createHttpLink({
uri: "http://localhost:4000/graphql",
credentials: 'include'
})
const client = new ApolloClient({
link,
cache: new InMemoryCache(),
});
export default client;
The solution to this problem is to actually set the corsOptions on the Apollo Server configuration.
apolloServer.applyMiddleware({ app, cors: corsOptions });

Cookie in Set-Cookie header not being set

I'm sending a request to a node.js server from a reactjs client using axios as shown below.
import axios from 'axios';
const apiClient = axios.create({
withCredentials: true,
baseURL: 'http://localhost:8080'
});
async function(payload) {
try {
debugger;
let result = await apiClient.post('/auth/signup/', payload);
debugger;
return result;
} catch (error) {
debugger;
throw error;
}
}
The node.js endpoint sets a cookie in the response as shown below.
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser')
const cors = require('cors');
const jwt = require('jsonwebtoken');
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
router.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['Set-Cookie', 'Date', 'ETag']} ));
router.use(cookieParser());
router.post('/signup', async (req, res, next) => {
debugger;
let database = req.app.locals.database;
try {
let user = await database.findByUsername(req.body.username);
let token = await jwt.sign({username: user.username}, config.secret, {expiresIn: "15m"});
res.cookie('jwt', token, {
maxAge: 900,
});
} catch (error) {
debugger;
return res.status(503).send({ auth: false, message: 'Database error.' });
}
});
The Set-Cookie header of the response contains the cookie as expected.
However, Chrome does not appear to be setting the cookie, as I cannot see the cookie in the Application window of the Developer Console.
I've looked at the answers to the following questions, which mention setting { withCredentials: true } in the axios configuration and not using a wildcard origin for cors in node.js, but I am already doing both.
Set-Cookie header not setting cookie in Chrome
Set cookies for cross origin requests
Any ideas as to why the cookie is not being set and how to fix this issue?
Though you are hosting client and server in the same domain as http://localhost, your ports are different, so the same-origin policy is failed here. You can check https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy.
As so, you making a CORS request, check your network tab in your developer tools in your current browser, you might see a preflight request OPTIONS, before your client sends POST request to your server.
The server must specify headers to accept the origin of your next request - POST request from http://localhost:8000 with method POST, you can refer to https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: POST // Your next request will use POST method
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true // cookies accepted
Added:
In Set-Cookie, Max-Age must be non-zero digit. It be rounded up into integer according to RFC doc. For express.js, cookies `maxAge property is on the scale of miliseconds
The solution will be set the maxAge property as second * 1000
res.cookie('jwt', token, {
maxAge: 10000,
});
Here's a repost of my answer on a similar question https://stackoverflow.com/a/62821342/8479303
In my case, the network panel showed that the response had the 'Set-Cookie' header, but in axios the header wouldn't show up, and the cookie was being set.
For me, the resolution was setting the Access-Control-Expose-Headers header.
For explanation, from this comment on an issue in the axios repository I was directed to this person's notes which led me to set the Access-Control-Expose-Headers header -- and now the cookie is properly setting in the client.
So, in Express.js, I had to add the exposedHeaders option to my cors middleware:
const corsOptions = {
//To allow requests from client
origin: [
"http://localhost:3001",
"http://127.0.0.1",
"http://104.142.122.231",
],
credentials: true,
exposedHeaders: ["set-cookie"],
};
...
app.use("/", cors(corsOptions), router);
It was also important that on the axios side I use the withCredentials config in following axios requests that I wanted to include the cookies.
ex/
const { data } = await api.get("/workouts", { withCredentials: true });

How to fix "Response to preflight request doesn't pass access control check: It does not have HTTP ok status" error in react app with nodejs api

I have been trying to do an api call (nodejs with express running on localhost) from a react app running in the browser over a local dev server (web-pack dev server). Everything was working well until I tried to call the api. They are both running on separate ports.
I have tried adding the cors headers (Recommended by MDN) to both the post call (from the app in browser) and to the response from the Nodejs API but neither of these solved the issue.
Code for the api call (in browser):
const headers = {
'Content-Type': 'application/json',
'access-token': '',
'Access-Control-Allow-Origin': '*',
}
export default async () => {
try {
const body = JSON.stringify({
test: true,
})
const response = await fetch('http://localhost:1337/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
console.log(response)
} catch (e) {
return e
}
}
API Middleware (in nodejs):
// Verify All Requests
app.use(VerifyToken)
// Compress
app.use(compression())
// Helmet middlware
app.use(helmet())
// Body Parser
app.use(bodyParser.urlencoded({
extended: false,
}))
app.use(bodyParser.json())
The expected result is to just give a 200 status code and respond with the data.
The actual output is:
OPTIONS http://localhost:1337/internal/provider/check_email_exist 404 (Not Found)
Access to fetch at 'http://localhost:1337/internal/provider/check_email_exist' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Since you're using webpack-dev-server you can use the proxy option DevServerProxy.
Your configuration will look like this:
// webpack.config.js
devServer: {
proxy: {
'/internal': 'http://localhost:1337'
}
}
Since I can't see your express routes on your question I'm speculating about the proxy route if your API lives on /internal endpoint then you should modify your React code like this:
const response = await fetch('/internal/provider/check_email_exist', {
method: 'POST',
headers,
body,
})
As you can see I ommited the https://localhost:1337 because the proxy option from webpack-dev-server will handle this and it will redirect to http://localhost:1337. Hope this will help you. Cheers, sigfried.
EDIT
As the comment on your question pointed out you should set the headers on your express server, not the client, for this task you can use the cors-middleware package.
Maybe this can help if you face with preflight errors.
My full config:
const cors = require('cors');
const express = require('express');
const { createProxyMiddleware: proxy } = require('http-proxy-middleware');
...
const logLevel = 'info';
const ip = require('ip').address();
const proxyOptions = {
xfwd: true,
target,
changeOrigin: true,
logLevel,
cookieDomainRewrite: {
'*': 'localhost',
},
headers: {
'X-Forwarded-For': ip,
'X-Node': 'true',
},
};
const backNginxApp = express();
backNginxApp.use(
cors({
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true,
})
);
backNginxApp.use('/api', proxy(proxyOptions));
API: const target = 'https://someapi.com'
Local development running at: http://localhost:3000

Resources