Issue
I have a react js application on node js, I can’t implement csurf on my application for a long time, how can I implement csurf in the right way?
project directory
-admin
-client
-config
-middleware
----file.js
----variables.js
-models
-node-modules
-routes
-utils
index.js
package-lock.json
package.json
main node js file
index.js
const express = require('express');
const config = require('config');
const path = require('path');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const mongoose = require('mongoose');
const helmet = require('helmet');
const compression = require('compression');
const taskRoutes = require('./routes/task');
const performerRoutes = require('./routes/performer');
const categoryRoutes = require('./routes/category');
const authRoutes = require('./routes/auth');
const knowRoutes = require('./routes/know');
const mobileRoutes = require('./routes/mobile');
const statisticsRoutes = require('./routes/statistics');
const businessRoutes = require('./routes/business');
const fileMiddleware = require('./middleware/file');
const varMiddleware = require('./middleware/variables');
const bcrypt = require('bcrypt');
const Admin = require('./models/admin');
const app = express();
const PORT = config.get('port') || 5000;
app.use(express.json({ extended: true }));
app.use(cookieParser());
app.use(csrf({ cookie: {
httpOnly: true,
maxAge: 3600 // 1-hour
}
}));
app.use(varMiddleware);
app.use(fileMiddleware.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'photosWorks' },
{ name: 'icon', maxCount: 1 }
]));
app.use(helmet());
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css",
"https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css",
"https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"],
fontSrc: ["'self'", "https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/fonts/"],
imgSrc: ["'self'"]
}
}));
app.use(compression());
app.use('/api/category/', categoryRoutes);
app.use('/api/know/', knowRoutes);
app.use('/api/new/task/', taskRoutes);
app.use('/api/performers/', performerRoutes);
app.use('/api/mobile/auth/', mobileRoutes);
app.use('/api/admin/', authRoutes);
app.use('/api/statistics/', statisticsRoutes);
app.use('/api/business/', businessRoutes);
if (process.env.NODE_ENV === 'production') {
app.use('/', express.static(path.join(__dirname, 'client', 'build')));
app.use('/', express.static(path.join(__dirname, 'admin', 'build')));
app.get('/admin/*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'admin', 'build', 'index.html'))
});
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
async function start() {
try {
await mongoose.connect(config.get('mongoUri'), {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
});
const candidate = await Admin.findOne();
if (!candidate) {
const hashPassword = await bcrypt.hash('666666', 12);
const admin = new Admin({
logAdmin: 'admin1234',
pasAdmin: hashPassword,
name: 'Sardor',
avatarUrl: '/images/cardImg2.jpg'
});
await admin.save();
}
app.listen(PORT, () => {
console.log(`Server on port ${PORT}`);
});
} catch (e) {
console.log(e);
}
}
start();
variables.js
module.exports = function(req, res, next) {
console.log('varMiddleware');
res.cookie('XSRF-TOKEN', req.csrfToken());
next();
}
this is a react component, in this component, how can I correctly implement csurf on the client side
for-business-page.js
import React, { Component } from 'react';
import { Link, Redirect } from 'react-router-dom';
import axios from 'axios';
import './for-business-page.css';
class ForBusinessPage extends Component {
state = {
name: '',
phone: '',
validBusiness: {},
successBusiness: false
}
bidHendler = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
axios.post('/api/business/bid', formData)
.then((response) => {
console.log(response);
this.setState({
name: '',
phone: '',
successBusiness: true
})
})
.catch((err) => {
const validBusiness = this.validHelper(err.response.data.errors);
this.setState({ validBusiness });
})
}
validHelper = (arrErrors) => {
let validName = '', validPhone = '';
for (let i = 0; i < arrErrors.length; i++) {
switch (arrErrors[i].param) {
case 'name':
validName = arrErrors[i].msg;
break;
case 'phone':
validPhone = arrErrors[i].msg;
break;
default:
continue;
}
}
return {
validName,
validPhone
};
}
inputControl = (e) => {
switch (e.target.name) {
case 'name':
this.setState({ name: e.target.value });
break;
case 'phone':
this.setState({ phone: e.target.value });
break;
default:
this.setState({ name: '', phone: '' });
}
}
render() {
const { name, phone, successBusiness } = this.state;
if (successBusiness) {
return <Redirect to="/success" />
}
return (
<div className="for-business-page">
<div className="container-fluid">
<div className="hat-business container">
<h1>Доставка для бизнеса</h1>
<p>Yams.uz осуществляет экспресс-доставку в мегаполисах, быстро, точно и надежно.</p>
<p>Курьеры доставят ваши посылки в любую погоду, праздники и выходные. Yams.uz — это курьерская служба к платформе которой, подключены сотни клиентов и десятки тысяч исполнителей.</p>
<a href="#bid" className="btn btn-primary">Отправить заявку</a>
</div>
<div className="bg-img-business">
<div className=" container">
<div />
</div>
</div>
<div className="business">
<div className="container">
<h2>Для вашего бизнеса</h2>
<div className="row">
<div className="col-12 mb-3 col-sm-10 mb-sm-3 offset-sm-1 col-md-6 offset-md-0 col-lg-4 mb-lg-0">
<div className="card">
<img src="/images/card-1.jpg" className="card-img-top" alt="для вашего бизнеса, одежда" />
<div className="card-body">
<h5 className="card-title">Для вашего бизнеса, Одежда</h5>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<Link to="/" className="btn btn-block btn-primary">Подробнее</Link>
</div>
</div>
</div>
<div className="col-12 mb-3 col-sm-10 mb-sm-3 offset-sm-1 col-md-6 offset-md-0 col-lg-4 mb-lg-0">
<div className="card">
<img src="/images/card-2.jpg" className="card-img-top" alt="аптеки" />
<div className="card-body">
<h5 className="card-title">Аптеки</h5>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<Link to="/" className="btn btn-block btn-primary">Подробнее</Link>
</div>
</div>
</div>
<div className="col-12 mb-3 col-sm-10 mb-sm-3 offset-sm-1 col-md-6 offset-md-0 col-lg-4 mb-lg-0">
<div className="card">
<img src="/images/card-3.jpg" className="card-img-top" alt="рассылка" />
<div className="card-body">
<h5 className="card-title">Рассылка</h5>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<Link to="/" className="btn btn-block btn-primary">Подробнее</Link>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="send-bid" id="bid">
<div className="container">
<h2>Отправить заявку</h2>
<p>Заполните данную форму и наши специалисты свяжутся
с вами в кратчайшие сроки, чтобы проконсультировать
вас о наших возможностях, предложить решение и подключить к системе</p>
<div className="row">
<div className="col-12 col-lg-6">
<div className="bid-form">
<form onSubmit={this.bidHendler}>
<input type="text"
className="form-control form-control-lg mb-3"
placeholder="Как вас зовут"
name="name"
onChange={this.inputControl}
value={name} />
<input type="phone"
className="form-control form-control-lg mb-3"
placeholder="Введите номер телефона"
name="phone"
onChange={this.inputControl}
value={phone} />
<button type="submit"
className="btn btn-lg btn-block btn-business">Отправить заявку</button>
</form>
</div>
</div>
</div>
<div className="bid-form-footer">
<p>или позвоните нам по телефону:</p>
<p>+998 (90) 777-77-77</p>
<p>(Пн — Вс, 9:00 – 21:00)</p>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default ForBusinessPage;
Help with the solution please, I will be very grateful!
Solution
Generate the csurf token and set it to cookie.
To do so, replace client route with
app.use(
'/',
(req, res, next) => {
res.cookie('csrf-token', req.csrfToken());
next();
},
express.static(path.join(__dirname, 'client', 'build'))
);
In React Component’s constructor, read csurf token from the cookie
import cookie from 'react-cookies';
...
constructor(props) {
super(props);
this.csrf = cookie.load('csrf-token');
}
Now you can send the token in the header with the request
axios.post('/api/business/bid', formDatam, headers: {
'csrf-token': this.csrf
})
.then((response) => {
console.log(response);
this.setState({
name: '',
phone: '',
successBusiness: true
})
})
.catch((err) => {
const validBusiness = this.validHelper(err.response.data.errors);
this.setState({ validBusiness });
})