Search code examples
expresssocketscsrfsveltekit

SvelteKit: Cross-site POST form submissions are forbidden


My setup is a SvelteKit app paired with an Express server (in order to handle sockets). This app has some POST requests which work fine with Vite, but not when I run it with Express. I get the error Cross-site POST form submissions are forbidden with a status code of 403 and a referrer policy "strict-origin-when-cross-origin".

So my question is very similar to this one, but the answers there didn't work for me (except for the insecure option to allow csrf). I also checked the SvelteKit documentation on the node adapter without success.

What I have tried:

  1. I added the variable ORIGIN=http://localhost:3000 node build/index.js to my .env file. I also added dotenv as a dependency and added dotenv.config() to my server file. But this has no effect.
  2. Setting csrf: {checkOrigin: false} in svelte.config.js fixed the issue, but this is not secure and not recommended.
  3. Running node -r dotenv/config build in the terminal. This prints out "Listening on 0.0.0.0:3000". But then my express server does not start at all. Perhaps the command needs to be added to the package.json, but I do not know where. There is no node build in the file.
  4. When I try to start my Express server with node -r dotenv/config server.js and open localhost:3000, I just get Invalid request body (code 400).

It sure is frustrating to setup websockets with SvelteKit, even after reading this blog post.

server.ts:

import express from "express";
import { handler } from "./build/handler.js";
import { attach_sockets } from "./sockets.js";
import dotenv from "dotenv";

dotenv.config();

const PORT = 3000;
const app = express();
const server = app.listen(PORT, () => {
    console.log("server is listening on port", PORT);
});
app.use(handler);

attach_sockets(server);

Solution

  • I've taken a look at your code and I have good and bad news... the good news is that you did nothing wrong. The bad news is that you can't get it working because of the way SvelteKit and NodeJS works (at least not in the intended way).

    Let me elaborate: From running your code, it is clear that the dotenv does not have any effect. Why is that? Quite simply, the .env file is loaded AFTER the ORIGIN value is requested by SvelteKit. This happens because you import the import { handler } from "./build/handler.js"; before running the dotenv.config();. Unfortunately, this module tries to resolve the ORIGIN immediately during import so there isn't much we can do here.... but, before you jump into changing the order, keep reading.

    The second issue you have is that NodeJS runs the import statements first so, even if you swap the lines so that the .env file is loaded before the import, it still would not work.

    So, what solution can we apply here? There are 2 options we can take: either inject the variable into the process without an .env file and then run the code or import the .env file and then run the code.

    In order to inject the variable before running the code you need to run your code like this:

    ORIGIN=http://localhost:3000 node server.js
    

    This time, this line is a command you need to run. If you run this line, you don't need the .env file (although it is not harmful and you can leave it if you have other variables).

    Instead, if you want to import the .env file completely, you need to run the command like this instead:

    NODE_OPTIONS='-r dotenv/config' node server.js
    

    At least you can update your package.json to include those changes in the start script so that you can continue using npm run start as an alias.