Express & Csurf rejects Vue (Axios) post request - express

I am currently dealing with forms and decided to test sending an email onchange mid registration to the server and give responsive feedback to users.
On Vue component creation I get the csrf token and store it for future posts. I attach it to the headers as 'X-CSRF-Token'. I send the token and still receive the invalid CSRF token error. I have verified the data in headers and the csrf token is in-fact being sent but just being rejected or the header is missing something.
Screenshot of error and response
//App.js
var createError = require('http-errors');
var cors = require('cors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var csurf = require('csurf')
var Mongoose = require('mongoose')
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var productRouter = require('./routes/products')
var app = express();
// DB things
var db
dbConnect();
app.use(cors())
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser())
app.use(csurf({ cookie:true }))
app.use('/api/', indexRouter);
app.use('/api/users', usersRouter);
app.use('/api/products', productRouter)
//index routes
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.json({
message:'respond with a resource blank',
});
});
router.get('/getCSRF/', function(req, res, next) {
res.json({
csrf:req.csrfToken(),
});
});
module.exports = router;
Below is the route I try posting to
// Users Check Email Post Route
router.post('/checkEmail',function(req,res,next){
email = req.body.email
console.log(email)
User.findOne(function(err,user){
if (err) { return res.json({ err:err })}
else { return res.json({ user:true }) }
})
})
Here is the method used in Vue to post
checkEmail: function () {
var headers = {
'Accept': 'application/json',
'Access-Control-Allow-Origin': '*' ,
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type,
Accept',
'Content-Type':'application/x-www-form-urlencoded',
'X-CSRF-Token':this.$store.getters.getCSRF,
}
axios.post('http://localhost:8000/api/users/checkEmail', {
email: this.user.Email
},headers )
.then(function (response) {
console.log(response)
})
.catch(function (error) {
console.log(error.response)
});
},
The issue is pretty common and I have gone through over 20 posts and tried their solutions but it did not help at all. I've tried using csurf independently on the same route(didn't work). I've tried all manner of headers. Your help will be greatly appreciated .
EDIT: Uploading to show proper headers pre-post. Pre-post logs of data

So I realized that csurf looks for two things . A cookie (this is not very apparent from the error but nevertheless) and the token to be sent via header . Axios by default apparently does not support cookies so I decided to move to a JWT header approach.

Your way of sending the token via router.get('/getCSRF/', ...) does not seem very secure to me, as any attacker could also easily get this token via this get request.
If you use app.use(csurf({ cookie:true })), then Express will validate every POST/PUT/DELETE request based on a cookie, but you need to set this cookie yourself.
(Csurf sets a cookie named _csrf but this is not the actual CSRF token)
I got it working this way:
Nodejs:
app.use(cookieParser());
app.use(
csrf({
// compare the XSRF-TOKEN cookie with the X-XSRF-TOKEN header on every post request
cookie: {
secure: true,
sameSite: 'strict',
},
})
);
app.use(function (req, res, next) {
// set the XSRF-TOKEN cookie so the client can send it back in the X-XSRF-TOKEN header
res.cookie('XSRF-TOKEN', req.csrfToken());
next();
});
I used Nuxt as my client. Nuxt.config.js:
modules: [
'#nuxtjs/axios',
],
axios: {
credentials: true, // this will take the XSRF-TOKEN from the cookie and set it in the X-XSRF-TOKEN header
baseURL: nuxtBaseUrl,
browserBaseURL: nuxtBrowserBaseUrl,
},
publicRuntimeConfig: {
axios: {
credentials: true, // this will take the XSRF-TOKEN from the cookie and set it in the X-XSRF-TOKEN header
baseURL: nuxtBaseUrl,
browserBaseURL: nuxtBrowserBaseUrl,
},
},
Alternative without nuxt:
const instance = axios.create({
baseURL: 'https://my-server/',
withCredentials: true, // this will take the XSRF-TOKEN from the cookie and set it in the X-XSRF-TOKEN header
});
instance.post('http://my-server/api/users/checkEmail', {
email: this.user.Email
}

Related

Invalid csrf token with NestJS

I would like to implement Csrf protection with NestJS and Quasar.
But I think I misunderstand something...
btw I'm not doing SSR, so I don't send the form from the back to the view.
Here is the NestJs back-end code:
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: true,
bodyParser: false,
});
console.log(`your App is listening on port ${PORT}`);
// Added Cookie-parser to user csurf packages
// Prevent CSRF attack
app.use(cookieParser());
app.use(csurf({ cookie: true }));
await app.listen(PORT);
}
bootstrap();
So I'm just using CookieParser and csurf package.
On my login page I call a "csrf endpoint" just to send a cookie to the view, to send it back with the post call (login).
I still get the "invalid csrf token" AND a CORS error and don't know why....(see screen below), any suggestions to make it works ?
When I try to login, error in the browser:
And error in the back-end:
Same error if I try a request with insomnia.
I thought that the CSRF token is attached to the "web browser" to go back to the back-end with nest request, so why I'm still getting this error ?
Insomnia send the cookie automatically with the right request so the token should go back to the back-end.
Any idea ?
Regards
EDIT:
After many times reading docs, It seems that CSRF protection is for SSR only ? No need to add csrf security with SPA ? Could anyone can confirm ?
EDIT: Here's another work:
The purpose here is to send a request before login to get a csrf token that I can put into a cookie to resend when I login with a POST method.
Here is my endpoint:
import { Controller, Get, Req, Res, HttpCode, Query } from "#nestjs/common";
#Controller("csrf")
export class SecurityController {
#Get("")
#HttpCode(200)
async getNewToken(#Req() req, #Res() res) {
const csrfToken = req.csrfToken();
res.send({ csrfToken });
}
}
Here is what I've done into my main.ts file (I'll explain below):
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: {
origin: "*",
methods: ["GET,HEAD,OPTIONS,POST,PUT"],
allowedHeaders: [
"Content-Type",
"X-CSRF-TOKEN",
"access-control-allow-methods",
"Access-Control-Allow-Origin",
"access-control-allow-credentials",
"access-control-allow-headers",
],
credentials: true,
},
bodyParser: false,
});
app.use(cookieParser());
app.use(csurf({ cookie: true }));
console.log(`your App is listening on port ${PORT}`);
await app.listen(PORT);
}
bootstrap();
And here my axiosInstance Interceptors of the request in my VueJS frontend:
axiosInstance.interceptors.request.use(
(req) => {
const token = Cookies.get('my_cookie')
if (token) {
req.headers.common['Authorization'] = 'Bearer ' + token.access_token
}
req.headers['Access-Control-Allow-Origin'] = '*'
req.headers['Access-Control-Allow-Credentials'] = 'true'
req.headers['Access-Control-Allow-Methods'] = 'GET,HEAD,OPTIONS,POST,PUT'
req.headers['Access-Control-Allow-Headers'] =
'access-control-allow-credentials,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,content-type,x-csrf-token'
const csrfToken = Cookies.get('X-CSRF-TOKEN')
if (csrfToken) {
req.headers['X-CSRF-TOKEN'] = csrfToken
console.log(req)
}
return req
},
(err) => {
console.log(err)
},
Here the same for repsonse:
axiosInstance.interceptors.response.use(
(response) => {
if (response?.data?.csrfToken) {
const {
data: { csrfToken },
} = response
Cookies.set('X-CSRF-TOKEN', csrfToken)
}
return response
},
And inside my login I make a call on the mounted function of my login component:
async mounted() {
const result = await securityService.getCsrf()
},
So now to explain:
As I said I'm not building a SSR project, that's why I want to send the token into a classic axios reponse and store it in a Cookie (this part is for test I heard that storing a csrf token into a classic cookie is not the right way.)
And for each next request I get the csrf token and "attach" it to the request into the headers, making my headers "custom".
Here is a problem I don't know how to make custom headers works with nestJS and CORS, that's why I try many thing with CORS options in NestJS and writte some custome header before the request go to the back-end but without success, I've got the same error message:
I'm a bit confuse about this problem and CORS/CSRF is a big deal for spa, my questions still the same, with CORS and SameSite cookie attributes, and my api is in a subdomain of my front-end, is it really necessary to make a anti-csrf pattern ?
Btw how can I make my custom headers working and why CORS say to me there is no "Access-Control-Allow-Origin" header but there is:
try to generate csrf token and pass to front on each petition
// main.ts - from NestJs - Backend
// after app.use(csurf({ cookie: true }))
app.use((req: any, res: any, next: any) => {
const token = req.csrfToken()
res.cookie("XSRF-TOKEN", token)
res.locals.csrfToken = token
next()
})
from: https://github.com/nestjs/nest/issues/6552#issuecomment-1175270849

HttpOnly cookie appears in response header but is not being saved to the browser

I recently built a simple real-time chat application with Nextjs on the frontend and Express on the backend. The frontend is deployed on vercel while the backend is deployed on heroku. When a user logs into the app, the backend generates a jwt token which is then sent via an HttpOnly cookie back to the frontend. Here is the code for said response:
const authenticate = async (req, res, next) => {
userService
.authenticate(req)
.then((user) => {
const { token, ...userInfo } = user;
res
.setHeader(
"Set-Cookie",
cookie.serialize("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
maxAge: 60 * 60 * 24,
sameSite: "none",
path: "/",
})
)
.status(200)
.json(userInfo);
})
.catch(next);
};
After authentication, each subsequent request to the backend is supposed to send the token to ensure the user is logged in. For example, this is the request sent to the server to get a chat between the logged in user and another user.
const getChat = async (id) => {
const identification = id;
const response = await axios.get(
`<SERVER_URL>/chats/chat/${identification}`,
{ withCredentials: true }
);
return response;
};
In development when on localhost:3000 for the frontend and localhost:4000 for the backend, everything works fine. However, when I deployed the frontend to vercel and the backend to heroku, the browser simply refuses to save the cookie! The jwt token appears in the response header after sending the authentication request, but it isn't saved to the browser. I have tried absolutely everything I can think of, including changing the cookie parameters, but I can't get it to work. I am pretty sure I have cors properly configured on the backend as well, along with the cookie-parser module:
const cors = require("cors");
const cookieParser = require("cookie-parser");
app.use(
cors({
origin: "<CLIENT_URL>",
credentials: true,
})
app.use(cookieParser());
Thanks for taking the time to read this, any help would be greatly appreciated! And my apologies if I have not elaborated enough, this is my first post here and I'm still trying to learn the proper etiquette of the site!
HttpOnly can not read or write on client-side but when the first HttpOnly send through a request other request on the same origin can access the coockies in backend but you should request in Next.js like this.
Next.js using fetch :
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();
then in backend you can read the coockie through coockie-parser
server.js:
const cookieParser = require("cookie-parser");
app.use(coockieParser());
route.post('/login',(req,res) => {
if(user){
res
.cookie("access_token", newToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production" ? true : false,
})
.status(200)
.json({ ok: true, payload: data });
}
})
Now you can read this cookie in other routes but sure about the expiration time.

Cookies from separate api with Next.js

I am currently making a Next.js app and I am having issues with cookies. I have an express API running on localhost:3001 which sets cookies when I signup/signin using express-cookie-session library. Whenever I do it through postman it works fine, however when I do it from next app an api it doesn't have "Set-Cookie" header in the response. I suspect it has to do with next app and express being on different ports and express being unable to set cookies to the next app however I'm unsure what to do about it. If it matters I wanted to set JWT's this way. It's possible to send them in response body but I would like to know how I could do it through the cookies.
Here are some relevant configurations:
app.use(cors());
app.set('trust proxy', true);
...
app.use(cookieSession({ signed: false, secure: false, sameSite: "lax" }));
a sign up controller:
const signUp = async (req: Request, res: Response, next: NextFunction) => {
...
const accessToken = TokenGenerator.generateAccessToken(user);
const refreshToken = TokenGenerator.generateRefreshToken(user);
user.refreshToken = refreshToken;
...
req.session = { accessToken, refreshToken };
res.send(user);
};
and getServerSideProps function
export const getServerSideProps = async (ctx) => {
const headers = ctx.req.headers;
const res = await axios.get("http://localhost:3001/users/current-user", {
headers,
});
return { props: { data: res.data } };
};
EDIT: Set-Cookie header is actually shown in chrome console however it isn't being console logged from axios response.
Here's example of cookie:
Set-Cookie: express:sess=eyJhY2Nlc3NUb2tlbiI6ImV5SmhiR2NpT2lKSVV6STFOaUlz
SW5SNWNDSTZJa3BYVkNKOS5leUpwWkNJNklqVm1abVEyTldSalpURXlaak5pT0RVellUWXlNR0
psT0NJc0ltVnRZV2xzSWpvaWJHdHNiaUlzSW1saGRDSTZNVFl4TURRME1qSXdOQ3dpWlhod0lq
b3hOakV3TkRReU1qRTVmUS5NN2szX1BVQy1hbzRQb2w4OXNiS05ndS1ndkpqNEVfUWdoX2RHSU
ZrZlZFIiwicmVmcmVzaFRva2VuIjoiZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhW
Q0o5LmV5SnBaQ0k2SWpWbVptUTJOV1JqWlRFeVpqTmlPRFV6WVRZeU1HSmxPQ0lzSW1WdFlXbH
NJam9pYkd0c2JpSXNJbWxoZENJNk1UWXhNRFEwTWpJd05IMC5JdHA2WHh4aFRPMWJUc0oydGNM
ZU9hdFB3cWZWdWRsVmRQWkNnejB3eS1rIn0=; path=/; domain=http://localhost:3000.
I found a solution to this by adding this line to my server configuration:
app.use(cors({ origin: "http://localhost:3000", credentials: true }));
as well as setting withCredentials to true on my axios request.

CORS --- Response to preflight request doesn't pass access control check

I'm having difficulty with getting CORS to work. I'm trying to fetch data from a Heroku app I set up. I'm fetching from Redux, and using ExpressJS for the backend. I've been looking through the CORS docs but still can't figure it out. enter link description here
This is how it looks in Express
var express = require('express');
var router = express.Router();
var cors = require('cors')
router.options('/', cors())
router.use((req, res, next) => {
res.append('Access-Control-Allow-Origin', ['*']);
res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.append('Access-Control-Allow-Headers', 'Content-Type');
next();
});
router.get('/', cors(), function(req, res, next) {
res.json({...})
}
I have the proxy set up in my app to http://localhost:3001/, but my app is running on 3000. I'm including this in case it could be the issue, but I don't know that it is.
My Redux file is set up like this
return dispatch => {
const url = "https://app-name.herokuapp.com/users";
return fetch(url, {
method: 'GET',
mode: 'cors',
headers: { 'Content-Type': 'text' }
})
The full error is: "Failed to load https://app-name.herokuapp.com/users: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access."
var express = require('express');
var app = express();
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,POST");
// res.header("Access-Control-Allow-Headers", "Content-Type");
next();
});

In express when reading cookies, it returns an empty object as {}

I'm trying to set cookie using res.cookie or res.append('Set-Cookie',...) on express.js, but when I use req.cookies, it return {}(which is the default value of res.cookies). I've searched many web pages but it still can't return the right value. PS: Express is 4.15.2
in server.js:
var express = require('express');
var session = require('express-session');//设置session
var cookieParser = require('cookie-parser');//解析cookie
var bodyParser = require('body-parser');
var app = express();
app.use(express.static('public'));
app.use(session({
secret: 'keyboard cat',
cookie: { maxAge: 60000 },
resave: false,
saveUninitialized: true
}));
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(cookieParser());
//设置跨域访问
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
// 查询所有博客数据
app.get('/getAll',function(req,res){
var pageNum = req.query.pageNum;
//连接数据库
query("some sql",function(err,vals,fields) {
console.dir(req.session.id);
// Cookies that have not been signed
console.log('Cookies: ', req.cookies)
// Cookies that have been signed
console.log('Signed Cookies: ', req.signedCookies)
res.send(vals);
});
});
//登陆验证
app.post('/login',function(req,res){
var username = req.body.username;
var password = req.body.password;
//过滤
var sql = "some sql";
query(sql,function(err,vals,fields) {
if(vals.length == 1){
//登录成功
//res.cookie('mycookie', '1234567890', { domain:'localhost',secure:false, maxAage:120000, httpOnly: true });
res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
// req.cookie('sid',req.session.id,{ maxAge: 900000, httpOnly: false});
res.send("ok");
}else{
//登陆失败
res.send("false");
}
});
});
app.get('/', function (req, res) {
res.sendFile( __dirname + "/"+ "index_prod.html" );
})
var server = app.listen(8081, function () {
var host = server.address().address
var port = server.address().port
console.log("应用实例,访问地址为 http://%s:%s", host, port)
})
When I login in, the result is as following:
cookie
but when I request for /getAll ,the console for cookies are: { }. (the default value, NOT the value we just write).
If the cookies and session have been correctly used, the reasons may be :
Make sure the client and the server obey the Same-origin policy. If not, make some changes.
Make sure your websites have no errors. (In the code above, there exit other errors such as 404 (Not Found) in the process of images fetching)