I have problems with setting up csrf. I hope that someone can point me in the right direction.
I'm using next.js with express.js.
When I refresh the page following happens:
I get a _csurf cookie (dev tools > application > cookies)
a csrf token is logged in my console (-> see last code snipped from context)
when I make a POST request (-> see routes/index.js f.ex. "/aignupQ"), I get the error "Invalid csurf token"; in the request header I can see the _csrf cookie
when I refresh the page and make the POST request again everything works.
I'm really confused by the error and really don't understand what is wrong. Here is some relevant code:
server.js:
require("dotenv").config();
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
const cookieParser = require("cookie-parser");
const routes = require('./routes');
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
//next.js configuration
const dev = process.env.NODE_DEV !== "production";
const nextApp = next({ dev });
const port = 3000;
const handle = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
app.get("*", (req, res) => {
return handle(req, res);
});
//start server
app.listen(port, (err) => {
if (err) throw err;
console.log(`listening on port ${port}`);
});
});
routes/index.js:
const express = require('express')
const router = express.Router()
const getCsrfToken = require('../controllers/csrf')
const postSignupQ = require("../controllers/postSignupQ");
const attachUser = require("../middleware/attachUser");
router.get("/csrfToken", getCsrfToken.getCsrfToken);
router.use(attachUser);
router.post("/signupQ", postSignupQ.postSignupQ);
module.exports = router
controllers/csrf.js
const getCsrfToken = (req, res) => {
res.json({ csrfToken: req.csrfToken() });
};
module.exports = { getCsrfToken };
context - here I console.log(csrf):
useEffect(() => {
const getCsrfToken = async() => {
const { data } = await axios.get('api/csrfToken');
console.log("csurf", data);
axios.defaults.headers['X-CSRF-Token'] = data.csrfToken;
};
getCsrfToken();
}, []);
I don't understand why I get the error message, when I make a POST request for the first time and when I refresh the page everything works. What's the problem and how can I solve this?
EDIT
Thanks to Jack Yu the code snippets above are working. Maybe it can help someone else..
EDIT
I also found your api path might be wrong. In your axios is await axios.get('csrfToken'). But, I saw /api/csrfToken in your router. Change it to await axios.get('/api/csrfToken')
Original Answer
In csurf package, when you use csurf({cookie: true}) with cookie mode in middleware at multiple times, it'll break the csrf token in response header with first time post. You could take a look for more detail in CSRF doesn't work on the first post attempt, I've explain the reason in that post. So, there are two solutions you could use.
Solution 1
According to the comments, you use app.use(csruf({cookie: true})) in server.js and router/index.js. Remove the following line in your router/index.js. When you setup csruf in server.js, you could use req.csrfToken() in controllers/csrf.js without setting up csruf again.
const csrf = require("csurf");
const csrfProtection = csrf({
cookie: true,
});
router.use(csrfProtection);
Solution 2
You'll need to use express-session package. Add following code before the csurf. If you have .use(csrf({cookie: true})) in your routes/index.js, remove it.
const session = require('express-session')
// mark1: change it to false
const csrfProtection = csrf({
cookie: false,
});
// blablabla ... other code
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use(cookieParser());
// mark2: put here
app.use(session({
name: "test",
secret: "test",
cookie: { maxAge: 3 * 60 * 60 * 1000 },
resave: false,
saveUninitialized: false
}))
// put here
app.use((err, req, res, next) => {
res.status(500).send("Something went wrong!");
});
app.use(csrfProtection);
app.use('/api', routes);
Then change {cookie: true} to {cookie: false} in all csurf setting. When you use session mode, you could use csruf({cookie: false}) many times in middleware.
We need to pass a cookie in header like below:
headerMaps.put("cookie", "_csrf=value; connect.sid=value; csrfToken=value")
Related
I have req.session variables that I am setting upon login, like so:
req.session.loggedin = true
req.session.firstname = loginDetails.firstName;
what I want to do is pass this information to ALL routes (I have nearly 60, and don't want to go through all of them and add these in manually), and also every route I have calls the front-end using this:
res.render('page.ejs', {data: rows}), so I would ideally want it to pass it to the front-end pages so I can access them there too. Not sure if this is possible, but worth a shot! thx 4 the help in advance!
You can create a middleware function and add variables to an existing express-session.
app.js
const express = require("express");
const session = require("express-session");
// express app
const app = express();
app.use(express.json());
// init example session with express session
app.use(session({ resave: true, secret: "123456", saveUninitialized: true }));
// use a middleware function
app.use((req, res, next) => {
if (!req.session.initialised) {
// init variables you want to set in req.session
req.session.loggedin = true;
req.session.firstname = "john doe";
}
next();
});
// test api endpoint
app.use("/testroute", require("./routes/api/test"));
// run server
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Then you can query your variables like req.session.firstname in any other route in the request object. You can also update it from your routes
test.js
// testroute
const router = express.Router();
router.get("/", async (req, res) => {
// get variables from request object
try {
console.log(req.session.firstname);
console.log(req.session.loggedin);
// this would update the session variable if uncommented
// req.session.firstname = "dagobert"
// console.log(req.session.firstname);
res.status(200).json({ message: `Your firstname is ${req.session.firstname}` });
} catch (error) {
res.status(400).json({ message: error.message });
}
});
module.exports = router;
I'm logging in on the front end with React. It goes to my node/Express/Passport Backend. Im able to log in successfully when it goes to the backend. but then im unable to communicate that back to the front end? I keep getting 404's and whatnot on the front end. I've tried everything, importing everything on the app.js (app.use) to the index.js (router.use) file. the only way i can get it to work is moving the router.post"login" to the app.js file and using it as a app.post, and then it only works when i put the route under the "app.use(routes)". My register post route works fine too, though. So im lost. I've delved into the cors and body parser stuff and the configs() and man, everything. its a deep deep rabbit hole. I need som e guidance here and explanation if thats ok. I've been working really hard on this and really need some mentor help here. I can include the passport middleware thats in another file, but i dont think thats the issue since im able to console.log ity logging me in at the end of the middleware, then console.logging in the last req res function. Thank you so much for helping!
p.s: Really the issue is "headers already sent", because when i try to sendStatus(200) in every which way possible, its not working (when the post route is "router.post". app.post works perfectly fine)
ps.s.s: youll probably see a lot of repeated and/or unneeded importing, but i dont want to fix that at the moment, that was just me trying every which way to get it to work. I can fix that later.
app.js
const express = require("express");
const app = express();
const mongoose = require("mongoose");
const session = require("express-session");
const passport = require("passport");
const crypto = require("crypto");
const routes = require("./routes");
const connection = require("./config/database");
const cors = require("cors");
app.use(cors({ origin: "http://localhost:3001", credentials: true }));
const User = mongoose.models.User;
const bodyParser = require("body-parser");
const MongoStore = require("connect-mongo")(session);
require("dotenv").config();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const sessionStore = new MongoStore({
mongooseConnection: mongoose.connection,
collection: "sessions",
});
app.use(
session({
secret: "zdfbdaf",
resave: true,
saveUninitialized: true,
store: sessionStore,
cookie: {
cookie: { secure: false },
maxAge: 1000 * 60 * 60 * 24,
},
})
);
require("./config/passport");
app.use(passport.initialize());
app.use(passport.session());
app.use(routes);
app.post(
"/login",
passport.authenticate("local"),
(req, res) => {
console.log("working");
res.status(200).send();
}
);
app.listen(3000);
index.js
const router = require("express").Router();
const passport = require("passport");
const bodyParser = require("body-parser");
const genPassword = require("../lib/passwordUtils").genPassword;
const connection = require("../config/database");
const mongoose = require("mongoose");
const User = mongoose.models.User;
const isAuth = require("./authMiddleware").isAuth;
// cors is needed with router.use else you have to put routes on the app.js
// const cors = require("cors");
// router.use(cors({ origin: "http://localhost:3001", credentials: true }));
// const isAdmin = require("./authMiddleware").isAdmin;
router.use(bodyParser.urlencoded({ extended: false }));
/**
* -------------- Post ROUTES ----------------
*
*/
router.post(
"/login",
passport.authenticate("local"),
(req, res) => {
console.log("working");
res.sendStatus(200);
}
);
router.post("/register", (req, res) => {
const saltHash = genPassword(req.body.repeatPassword);
const salt = saltHash.salt;
const hash = saltHash.hash;
const newUser = new User({
username: req.body.email,
hash: hash,
salt: salt,
});
newUser.save().then((user) => {});
res.sendStatus(200);
});
module.exports = router;
Try to remove the app.post method call, and use this to define sub-router routes.
app.use('/', routes);
Currently, you have two handlers for the same route, that can cause bugs.
I am using express 4.
in my server.js I have express.json() middleware
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const errorHandler = require('./_helpers/error-handler');
const app = express();
const port = process.env.PORT || 5000;
app.use(cors());
app.use(express.json()); /////////////////////////////////////////
const uri = process.env.ATLAS_URI;
mongoose.connect(uri, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, useFindAndModify: true});
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB database connection established successfully");
});
// routers
app.use('/api/users', require('./routes/api/users/users.controller'));
app.use('/api/orders', require('./routes/api/orders/orders.controller'));
app.use('/shopify/app', require('./routes/shopify/app/shopify.controller'));
app.use('/shopify/app/webhooks', require('./routes/shopify/app/webhooks/webhooks.controller')); ///////////////
app.use(errorHandler);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
but for '/shopify/app/webhooks' route I need to get raw body so I can create hash
but so far I am receiving Object because I have express.json() middleware.
this is my webhooks.controller.js file
const express = require('express');
const router = express.Router();
const crypto = require('crypto')
const SHOPIFY_API_SECRET_KEY = process.env.SHOPIFY_API_SECRET_KEY;
// router.use(express.raw({ type: "application/json"}));
// routes goes here
router.post('/app/uninstalled', express.raw({ type: 'application/json' }), async (req, res, next) => {
const hmac = req.get('X-Shopify-Hmac-Sha256')
console.log(req.body);
// create a hash using the body and our key
const hash = crypto
.createHmac('sha256', SHOPIFY_API_SECRET_KEY)
.update(req.body, 'utf8', 'hex')
.digest('base64')
// Compare our hash to Shopify's hash
if (hash === hmac) {
// It's a match! All good
console.log('Phew, it came from Shopifify!')
res.sendStatus(200)
} else {
// No match! This request didn't originate from Shopify
console.log('Danger! Not from Shopify!')
res.sendStatus(403)
}
})
what I have tried is in webhooks.controller.js router.use(express.raw({type: "application/json"}))
i thought since I am receiving json object I can use express.raw() middleware that accepts json
but it's still not working.
You have to place this route BEFORE your app.use(express.json()) middleware and then you can apply the raw middleware directly to that route:
app.use('/shopify/app/webhooks', express.raw({/* put your options here */}), require('./routes/shopify/app/webhooks/webhooks.controller'));
Keep in mind that this line of code must go physically before your express.json() middleware.
We can get useful info for specific routes before applying body parser.
So, if you want to get raw body for stripe webhooks.
We can do like this.
app.use(bodyParser.json({
extended: true,
verify: function (req, res, buf) {
if (req.originalUrl.endsWith('/stripe/webhooks')) {
req.rawBody = buf
}
}
}))
I'm making a POST request from a React front-end using axios to an endpoint to save some data to my DB (MongoDB). I'm getting an error that one cannot read property 'name' of undefined. I think that's occurring because req.body is undefined but I can't understand what's wrong with my axios request. I logged all the parameters and they are there (not undefined). The axios request and the endpoint are written below. Any help will be appreciated. Thanks!
Axios Request
const uploadElement = async (name, HTMLCode, JSCode, CSSCode, screenshot) => {
console.log(name)
try {
await axios({
method: 'post',
url: '/api/elements',
data: {
name: name,
HTMLCode,
JSCode,
CSSCode,
screenshot
}
});
} catch (e) {
console.log(e);
}
}
Endpoint for POST Request
router.post("/", upload.single("screenshot"), async (req, res) => {
try {
const newElement = new Element({
name: req.body.name,
JSCode: req.body.JSCode,
HTMLCode: req.body.HTMLCode,
CSSCode: req.body.CSSCode,
screenshot: req.file.buffer,
});
await newElement.save();
res.send("Data uploaded successfully!");
} catch (e) {
console.error(e);
}
});
Server.js
const express = require("express");
const passport = require("passport");
const session = require("express-session");
const cors = require('cors');
const elementRouter = require("./routes/elementRoute");
const authRouter = require("./routes/authRoute");
const connectDB = require("./config/db");
const app = express();
const port = process.env.PORT || 5000;
connectDB();
app.use(
session({
secret: "googleOAuth",
resave: false,
saveUninitialized: true,
})
);
app.use(cors());
// Passport Config
require("./config/passport")(passport);
app.use(passport.initialize());
app.use(passport.session());
app.use("/api/elements", elementRouter);
app.use("/api/auth", authRouter);
app.listen(port, () => {
console.log(`Server is up on port ${port}`);
});
You need to install and require body-parser in your serverside code
First run npm i --save body-parser
Then require it like this
const bodyParser = require("body-parser");
Then use it after you declare your app ( after this line const app = express();)
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
This makes the data of your request available in req.body
I'm trying to run a simple query from an express route:
var router = require('express-promise-router')()
const { Pool } = require('pg')
const pool = new Pool({
user: 'user',
password: 'password',
host: 'host',
port: 1234,
database: 'db'
})
router.get('/', async (req, res) => {
console.log('OK')
try {
const { rows } = await pool.query('Select VERSION()')
console.log(rows)
}
catch(e) {
console.log(e)
}
console.log('DONE')
})
module.exports = router
'OK' Prints after sending the request but rows, e, or 'DONE' never print. I'm following the async/await method directly from https://node-postgres.com/guides/async-express.
I've also came across a thread for koa-router where people were having issues with async await calls because of some middle-ware they added that wasn't synchronous
https://github.com/ZijianHe/koa-router/issues/358.
I'm not sure what middle-ware would cause this but here's my app.js that initializes all middle-ware:
var createError = require('http-errors');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require("cors");
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var dataRouter = require("./routes/data");
var uploadRouter = require("./routes/upload")
var fundingRouter = require('./routes/chartData/fundingOverview')
var testRouter = require('./routes/test')
var authRouter = require('./routes/auth')
var session = require('express-session')
var MongoStore = require('connect-mongo')(session)
var passport = require('passport')
const config = require('config')
const mongo = config.get('mongo')
const mongoose = require('mongoose')
mongoose.connect(mongo, {
useUnifiedTopology: true,
useNewUrlParser: true,
useFindAndModify: false
}).then(res => {
console.log('connected')
}).catch(err => {
console.log(err)
})
var express = require('express');
const mountRoutes = require('./routes')
var app = express();
const bodyParser = require('body-parser')
app.use(bodyParser.json())
mountRoutes(app)
app.use(cors())
var sessionMiddleWare = session({
secret: 'top session secret',
store: new MongoStore({ mongooseConnection: mongoose.connection }),
resave: true,
saveUninitialized: true,
unset: 'destroy',
cookie: {
httpOnly: false,
maxAge: 1000 * 3600 * 24,
secure: false, // this need to be false if https is not used. Otherwise, cookie will not be sent.
}
})
app.use(sessionMiddleWare)
// Run production React server on Node server
if(process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
})
}
// End Run production React Server on Node Server
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// app.use('/upload', uploadRouter)
// app.use('/', indexRouter);
// app.use('/users', usersRouter);
// app.use('/data', dataRouter)
// app.use('/funding', fundingRouter)
// app.use('/login', usersRouter)
// app.use('/auth', authRouter)
// catch 404 and forward to error handler
// app.use(function(req, res, next) {
// next(createError(404));
// });
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
I'm mounting the routes directly after body parser. That's the only middle-ware that's called before the routes and is required in order for me to get data into the back end.
I'm able to execute that simple query by putting it into a script file and running 'node test.js' (I.E without the router) and it works fine so I know it's not a problem with node-postgre.
I know this is a problem with the call stack not being totally synchronous but I'm confused as to what's not at this point. I even made the axios call on the front-end async/await with no luck (I don't think it was necessary though).
Any guidance would be help a lot.
EDIT:
I created a fresh express skeleton and hooked my front-end to make a call to a route on the new express server with the same code, it worked. It led me to discover the call wasn't being completed because I was running the server with Nodemon. When I start the server using 'yarn start' the async calls get processed correctly. The question now is what in nodemon makes async router calls not work?
You need to finish the request/response cycle in your middleware.
So in your code:
router.get('/', async (req, res) => {
console.log('OK')
try {
const { rows } = await pool.query('Select VERSION()')
console.log(rows)
res.status(200).json(rows)
}
catch(e) {
console.log(e)
res.status(500).json(e)
}
console.log('DONE')
})