简体   繁体   中英

Express.js Csurf working in postman but not React.js

I'm trying to setup CSRF tokens so that I can do a number of checks before issueing a token to the client to use in future requests.

Taking the guidance from the csurf documentation , I've setup my express route with the following:

const express = require('express');
const router = express.Router({mergeParams: true});
const csurf = require('csurf');
const bodyParser = require('body-parser');
const parseForm = bodyParser.urlencoded({ extended: false });

const ErrorClass = require('../classes/ErrorClass');

const csrfMiddleware = csurf({
    cookie: true
});

router.get('/getCsrfToken', csrfMiddleware, async (req, res) => {
    try {
        // code for origin checks removed for example
        return res.json({'csrfToken': req.csrfToken()});
    } catch (error) {
        console.log(error);
        return await ErrorClass.handleAsyncError(req, res, error);
    }
});

router.post('/', [csrfMiddleware, parseForm], async (req, res) => {
    try {
        // this returns err.code === 'EBADCSRFTOKEN' when sending in React.js but not Postman
    } catch (error) {
        console.log(error);
        return await ErrorClass.handleAsyncError(req, res, error);
    }
});

For context, the React.js code is as follows, makePostRequest 100% sends the _csrf token back to express in req.body._csrf

  try {
            const { data } = await makePostRequest(
                CONTACT,
                {
                    email: values.email_address,
                    name: values.full_name,
                    message: values.message,
                    _csrf: csrfToken,
                },
                { websiteId }
            );

        } catch (error) {
            handleError(error);
            actions.setSubmitting(false);
        }

Postman endpoint seems to be sending the same data, after loading the /getCsrfToken endpoint and I manually update the _csrf token.

邮递员例子

Is there something I'm not doing correctly? I think it may be to do with Node.js's cookie system.

I think your problem is likely to be related to CORS (your dev tools will probably have sent a warning?).

Here's the simplest working back-end and front-end I could make, based on the documentation:

In Back-End (NodeJS with Express) Server:

In app.js:

var cookieParser = require('cookie-parser')
var csrf = require('csurf')
var bodyParser = require('body-parser')
var express = require('express')
const cors = require('cors');

var csrfProtection = csrf({ cookie: true })
var parseForm = bodyParser.urlencoded({ extended: false })

var app = express()

const corsOptions = {
  origin: "http://localhost:3000",
  credentials: true,
}
app.use(cors(corsOptions));

app.use(cookieParser())

app.get('/form', csrfProtection, function (req, res) {
  res.json({ csrfToken: req.csrfToken() })
})

app.post('/process', parseForm, csrfProtection, function (req, res) {
  res.send('data is being processed')
})

module.exports = app;

(make sure you update the corsOptions origin property to whatever your localhost is in React.

In Index.js:

const app = require('./app')

app.set('port', 5000);

app.listen(app.get('port'), () => {
    console.log('App running on port', app.get('port'));
});

In React:

Create file "TestCsurf.js" and populate with this code:

    import React from 'react'
    
    export default function TestCsurf() {
    
        let domainUrl = `http://localhost:5000`
        const [csrfTokenState, setCsrfTokenState] = React.useState('')
        const [haveWeReceivedPostResponseState, setHaveWeReceivedPostResponseState] = React.useState("Not yet. No data has been processed.")
    
        async function getCallToForm() {
            const url = `/form`;
            let fetchGetResponse = await fetch(`${domainUrl}${url}`, {
                method: "GET",
                headers: {
                    Accept: "application/json",
                    "Content-Type": "application/json",
                    "xsrf-token": localStorage.getItem('xsrf-token'),
                },
                credentials: "include",
                mode: 'cors'
            })
            let parsedResponse = await fetchGetResponse.json();
            setCsrfTokenState(parsedResponse.csrfToken)
        }
    
        React.useEffect(() => {
            getCallToForm()
        }, [])
    
        async function testCsurfClicked() {
            const url = `/process`
            let fetchPostResponse = await fetch(`${domainUrl}${url}`, {
                method: "POST",
                headers: {
                    Accept: "application/json",
                    "Content-Type": "application/json",
                    "xsrf-token": csrfTokenState,
                },
                credentials: "include",
                mode: 'cors',
            })
            let parsedResponse = await fetchPostResponse.text()
            setHaveWeReceivedPostResponseState(parsedResponse)
        }
    
        return (
            <div>
                <button onClick={testCsurfClicked}>Test Csurf Post Call</button>
                <p>csrfTokenState is: {csrfTokenState}</p>
                <p>Have we succesfully navigates csurf with token?: {JSON.stringify(haveWeReceivedPostResponseState)}</p>
            </div>
        )
    }

Import this into your app.js

import CsurfTutorial from './CsurfTutorial';

function App() {
  return (
    <CsurfTutorial></CsurfTutorial>
  );
}

export default App;

That's the simplest solution I can make based on the CSURF documentations example. It's taken me several days to figure this out. I wish they'd give us a bit more direction!

I made a tutorial video in case it's of any help to anyone: https://youtu.be/N5U7KtxvVto

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