简体   繁体   中英

Express.js API w/ Next.js: csurf implementation

Summary:

I have a complete web app built with a few separate apps:

  1. A next.js customer facing app and
  2. express.js web api.
  3. Oh and I'm using nginx as a reverse proxy to serve these both up, not sure if that is interfering but it seems that it is not interfering to me.

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:

  • Express.js web api, the csurf imp (csurf imp commented out, since it doesn't work, was trying to use on all api routes)

   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!

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM