I have an SSR react application with nodejs backend, and i can't implement csurf protection. I read a thousands of articles but can't figure out where or how should i put the protection. Should i implement a GET method with one endpoint just for csrf both in backend and frontend?
If anyone have an idea that would be really helpful for me.
Backend: index.js
const port = 9004;
const app = new express();
app.use(cookieParser())
app.use(compression());
app.use(cors({ origin: '*', credentials: true}));
app.use(jwtMiddleware());
app.use(bodyParser.urlencoded({ extended: false, limit: '200mb' }));
app.use(bodyParser.json({ limit: '200mb' }));
const csrfProtection = csrf({ cookie: true })
app.use(csrfProtection)
const getCsrf = function (req, res, next) {
const token = req.csrfToken()
res.cookie('csrf-token', token)
res.send({ csrfToken: token });
next()
}
app.use(getCsrf)
withApollo(app, schema, resolvers).listen(port, () => {
console.log(`GRAPHQL Server is now running on ${port}/graphql`);
});
Apollo middleware:
export default (app, typeDefs, resolvers) => {
const apollo = new ApolloServer({
typeDefs,
resolvers,
csrfPrevention: true,
context: ({req}) => ({
user: req.user,
context: req.headers['x-context'],
domain: req.headers['x-domain']
}),
});
apollo.applyMiddleware({app});
return app;
};
React server:
export default (context, io) => {
const app = new express();
const upload = multer();
const shouldCompress = (req, res) =>
req.headers["x-no-compression"] ? false : compression.filter(req, res);
app.use(contextMiddleware(context));
app.use(cookieParser());
app.use(bodyParser.json({ limit: "200mb" }));
app.use(bodyParser.urlencoded({ limit: "200mb", extended: true }));
app.use(upload.any());
app.use(robots({ UserAgent: "*", Disallow: "/" }));
app.use(cors({ origin: "*", methods: "GET,HEAD,PUT,PATCH,POST,DELETE",credentials: true }));
app.use(compression({ level: 2, filter: shouldCompress }));
app.use(
session({
secret: "2C44-4D44-WppQ38S",
cookie: { maxAge: 2628000000 },
resave: true,
saveUninitialized: true,
})
);
app.use(
"/public",
express.static(path.resolve(process.cwd(), "public"), { maxAge: 3600000 })
);
app.use("/assets", express.static(path.resolve(process.cwd(), "assets")));
app.use("/api/admin", adminController);
app.use("/api/login", loginController);
app.get("*", async (req, res) => {
const branch = matchRoutes(appRoutes, req.path);
const hasUser = hasSessionUser(req.session);
const hasToken = hasUser ? await validateSessionUser(req) : false;
if (req.path === "/") return res.redirect("/login");
if (req.path === "/login" && hasUser && hasToken)
return res.redirect("/admin");
const store = createStore(req);
const promises = branch.map(({ route }) =>
route.loadData ? route.loadData(store) : Promise.resolve(null)
);
Promise.all(promises).then(() => {
const reqContext = {};
const html = render(req, store, reqContext);
if (reqContext.notFound) res.redirect("/error");
return res.send(html);
});
});
return app;
};
In frontend the graphql connected post method:
const request = ({ body, headers = {}, baseUrl } = {}) =>
axios({
method: "POST",
baseURL: baseUrl,
url: 'graphql',
data: body,
headers,
withCredentials: true,
}).catch(error => {
if (!error.response) throw error;
return error.response;
});
const apiWrapper = async ({ body, headers = {}, baseUrl = null }) => {
const result = await request({ body, headers, baseUrl }).catch(error => ({
status: undefined,
data: { code: 'NETWORK_ERROR', message: error.message },
}));
return result
}
export default apiWrapper;