Skip to content

Trying to put CSRF token not only in form pages, but also in layout. Invalid CSRF token literally everywhere #125

@IArnaut2

Description

@IArnaut2

I have made doubleCSRFprotection middleware and a middleware that sends the CSRF token in every page.
realestate\middleware\csrfToken.ts:

const {
  generateCsrfToken, // Use this in your routes to provide a CSRF token.
  doubleCsrfProtection // This is the default CSRF protection middleware.
} = doubleCsrf({
  getSecret: () => process.env.CSRF_SECRET!,
  getSessionIdentifier: (req: any) => req.session.id
});

function csrfToken(req: Request, res: Response, next: any) {
  res.locals.csrfToken = generateCsrfToken(req, res);
  next();
}

export { doubleCsrfProtection, csrfToken };

I have put the doubleCsrfProtection in app.ts and also csrfToken to have the CSRF token be accessed globally, especially in the global header.
realestate\app.ts:

const app = express();
const __dirname = import.meta.dirname;

app.use(
  session({
    secret: process.env.SESSION_SECRET!,
    resave: true,
    saveUninitialized: true
  })
);
app.use(cookieParser(process.env.COOKIE_SECRET!));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(join(__dirname, "public")));
app.disable("x-powered-by");

// view engine setup
app.set("view engine", "ejs").set("views", join(__dirname, "views"));

// 4 CSRF tokens
app.use(doubleCsrfProtection);
app.use(csrfToken);

realestate\views\partials\header.ejs:

<body class="d-flex flex-column">
  <!-- Navbar -->
  <nav class="navbar navbar-expand-md bg-secondary sticky-top py-0 mx-0 mb-3">
    <div class="container-fluid">
      <!-- ... -->

      <div class="collapse navbar-collapse" id="toggler">
        <!-- ... -->

        <ul class="navbar-nav">
          <!-- 1 Authentication & authorization -->
          <% if (auth) { %>
          <li class="nav-item btn btn-secondary px-md-1 py-md-3">
            <a class="nav-link fw-semibold text-light" href="/korisnici/<%= auth.id %>">Profil</a>
          </li>
          <li class="nav-item btn btn-secondary px-md-1 py-md-3">
            <form action="/odjava" method="post">
              <!-- 4 CSRF tokens -->
              <input type="hidden" name="_csrf" value="<%= csrfToken %>">
              <input class="nav-link fw-semibold text-light" type="submit" value="Odjava">
            </form>
          </li>
          <% } else { %>
          <li class="nav-item btn btn-secondary px-md-1 py-md-3">
            <a class="nav-link fw-semibold text-light" href="/registracija">Registracija</a>
          </li>
          <li class="nav-item btn btn-secondary px-md-1 py-md-3">
            <a class="nav-link fw-semibold text-light" href="/prijava">Prijava</a>
          </li>
          <% } %>
        </ul>
      </div>
    </div>
  </nav>

Other templates where I use CSRF token:
realestate\views\listings\listing_create.ejs:

<%- include("../partials/header", { title: "Postavka novog oglasa" }) %>
<h1 class="fw-bold mb-3">Postavka novog oglasa</h1>

<div class="row">
  <div class="col-md-6 col-lg-8">
    <form class="border border-secondary-subtle rounded-3 p-3 mb-3" action="/oglasi/postavka" method="post" novalidate>
      <!-- 4 CSRF tokens -->
      <input type="hidden" name="_csrf" value="<%= csrfToken %>">

      <!-- ... -->

      <input class="btn btn-primary fw-semibold" type="submit" value="Postavi">
    </form>
  </div>
</div>
<%- include("../partials/footer") %>

realestate\views\auth\login.ejs:

<%- include("../partials/header", { title: "Prijava" }) %>
<h1 class="fw-bold mb-3">Prijava</h1>

<div class="row">
  <div class="col-md-6 col-lg-4">
    <form class="border border-secondary-subtle rounded-3 p-3 mb-3" action="/prijava" method="post" novalidate>
      <p class="mb-3">Nemaš nalog? <a href="/registracija">Registruj se</a></p>

      <!-- 4 CSRF tokens -->
      <input type="hidden" name="_csrf" value="<%= csrfToken %>" />

      <!-- ... -->

      <input class="btn btn-primary fw-semibold" type="submit" value="Prijavi se" />
    </form>
  </div>
</div>
<%- include("../partials/footer") %>

realestate\views\partials\listings\list\footer.ejs (I also use CSRF tokens in partials):

<!-- Card footer -->
<div class="card-footer d-flex justify-content-between align-items-center">
  <span class="fw-lighter">
    <% if (listing.isUpdated()) { %>
    Ažuriran: <%= moment(listing.updatedAt).format("DD.MM.YYYY HH:MM") %>
    <% } else { %>
    Postavljen: <%= moment(listing.createdAt).format("DD.MM.YYYY HH:MM") %>
    <% } %>
  </span>

  <form action="/oglasi/cuvanje/<%= listing.id %>" method="post">
    <!-- 4 CSRF tokens -->
    <input type="hidden" name="_csrf" value="<%= csrfToken %>">

    <button class="btn btn-link" type="submit">
      <% if (listing.isSaved(authId)) { %>
      <i class="bi bi-bookmark-fill text-success fs-4"></i>
      <% } else { %>
      <i class="bi bi-bookmark text-success fs-4"></i>
      <% } %>
    </button>
  </form>
</div>

For example, when I press submit button in Login form, the CSRF token is invalid.

ForbiddenError: invalid csrf token
    at doubleCsrf (file:///D:/Documents/_Dokumenti/Projekti/_Master/Real%20Estate%20Listings%20App/Secure/Node/realestate/node_modules/csrf-csrf/dist/index.js:30:33)
    at <anonymous> (D:\Documents\_Dokumenti\Projekti\_Master\Real Estate Listings App\Secure\Node\realestate\middleware\csrfToken.ts:8:5)
    at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:660:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Project repo: https://github.com/IArnaut2/Express-Realestate-CSRF-Issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions