Reputation: 101
Summary:
I have a complete web app built with a few separate apps:
Everything is working very well as expected except for one thing: the gosh darn csurf implementation. I cannot get it to work and have tried and tried. So, I'm here for help.
About the apps:
The next.js app renders everything customer facing. It has a custom server which doesn't do more than use helmet and a "get-user" request to my express.js web api to populate req.user and respond to my next.js app with a "user" object to render private routes.
The express.js web api manages user sessions (which is working great so far btw) over http only secure cookies set to the next.js headers and the next.js client includes these for requests to the express.js web api.
The problem:
Everything works great but when I try to use the npm module name "csurf" in my express.js web api to set a csrf cookie for my next.js customer facing app, I cannot get this to work. I'm successfully setting the csrf header and getting that value and I use redux to set it in the store. Then, when I try to get the csrftoken and apply it to a client request, that request is denied.
Here are brief code implementations:
import './env';
import express from 'express';
import csrf from 'csurf';
import cors from 'cors';
import morgan from 'morgan';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import helmet from 'helmet';
import redis from 'redis';
import connectRedis from 'connect-redis';
import session from 'express-session';
import * as configs from '../config';
import authRouter from './routes/authentication';
import userRouter from './routes/user';
import { setupLocalAuth } from './middleware/auth.middleware';
const RedisStore = connectRedis(session);
const client = redis.createClient({ host: configs.REDIS_CLIENT_HOST });
const port = process.env.PORT || 5000;
const app = express();
app.use(cors({ origin: configs.CORS_ORIGIN, credentials: true }));
if (configs.IS_DEV) {
app.use(morgan('dev'));
}
app.use(helmet());
app.use(cookieParser());
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(
session({
name: configs.SESSION_NAME,
secret: configs.SESSION_SECRET,
store: new RedisStore({ client }),
saveUninitialized: false,
resave: false,
proxy: configs.IS_DEV ? false : true,
cookie: {
domain: configs.SESSION_COOKIE_DOMAIN,
httpOnly: true,
secure: configs.SESSION_SECURE,
maxAge: 14 * 24 * 60 * 60 * 1000, // 14 days
},
}),
);
/// /////////////////////////////////////////////////////////////////////////////////////////////
//
//
/// /////////////////////////////////////////////////////////////////////////////////////////////
// app.use(csrf({ cookie: true }));
// app.use(function(req, res, next) {
// const token = req.csrfToken();
// res.cookie('XSRF-TOKEN', token);
// // res.locals._csrf = token;
// // res.locals.csrfToken = token;
// next();
// });
if (!configs.IS_DEV) {
app.set('trust proxy', 1);
}
setupLocalAuth(app);
app.use('/api/auth', authRouter);
app.use('/api/user', userRouter);
app.use('/api/facility', facilityRouter);
app.use('/api/team', teamRouter);
app.use('/api/issues', issueRouter);
app.use('/api/forge', forgeRouter);
// error handlers
// development error handler
// will print stacktrace
if (process.env.NODE_ENV !== 'test') {
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
res.status(err.status || 500);
res.json({
message: err.message,
error: err,
});
});
}
// production error handler
// no stacktraces leaked to user
if (process.env.NODE_ENV === 'production') {
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
res.status(err.status || 500);
res.json({
message: err.message,
error: {},
});
});
}
app.listen(port, () => {
console.log(`API: Listening on port: ${port}`);
});
Then, on the Next.js side:
All I do is collect the csrf tokens and put them in my redux store for api requests. One thing though: when I console log the csrf tokens from the express.js web api and what I've received on the next.js side, they never match. So I think that is the culprit but the dang thang doesn't make sense to me. I'm making a mistake somewhere.
One big question remains, do I even need to do this for SPA approach? I set up a Django app in like 4 days and this was sooooo easy as the html was rendered traditionally on the server.
Thanks a bunch!
Upvotes: 0
Views: 1821
Reputation: 101
I figured it out.
What I needed to do was to grab the " res.cookie('XSRF-TOKEN', token);" cookie in my front-end js app and put that in my req.body as _csrf. Now it works!
Upvotes: 0