Help with sveltekit csrf and Railway

willkesslerHOBBY

3 months ago

Hello, deploying my Svelte4 + superforms app on railway I get '403 Cross-site POST is forbidden' when submitting a form. I've tracked this down to csrf handling in sveltekit. Specifically, even though my site is served on https, when the form submits, the request origin received is http.

Request URL: http://frontend-staging-1e0f.up.railway.app/manage/fleet/drivers/driver/397a54e7-f60a-49ba-8578-55176b334ab3

  'sec-fetch-mode': 'cors',

Request origin: http://frontend-staging-1e0f.up.railway.app

  'sec-fetch-site': 'same-origin',

I don't understand how this happens. I've tried using x-forwarded-proto in my hooks.server.ts to try to adapt:

  const forwardedProto = event.request.headers.get('x-forwarded-proto');
  if (forwardedProto) {
    // Update the URL protocol to match the original client request
    event.url.protocol = `${forwardedProto}:`;
  }

and even verified that: X-Forwarded-Proto: https

but this hack doesn't make any difference.

Disabling CSRF in sveltekit solves the problem, but ofc I don't want to do that. I'm fully stumped. I'm 99% sure this is a railway issue, but not sure how to solve. Please help! Spent hours debugging this and no closer except to open a security hole here.

Solved

0 Replies

willkesslerHOBBY

3 months ago

i will also note that in the sample sveltekit app for railway, csrf is disabled. not sure why



3 months ago

we do indeed demux incoming public https requests down to http and set the X-Forwarded-Proto to https, so you just need to find a reliable way to tell sveletkit that the incoming request is indeed https


willkesslerHOBBY

3 months ago

thank you for responding so quickly and on a sunday morning no less.

Claude is suggesting i can do this:

adapter: adapter({ protocol_header: 'X-Forwarded-Proto', host_header: 'X-Forwarded-Host', proxy_header: 'X-Forwarded-Proto', // Add this line }), csrf: { checkOrigin: true, },

do you have any other customers who's solved this?


3 months ago

we don't set x- forwarded-host but can't hurt to set the other two


willkesslerHOBBY

3 months ago

turns out claude was wrong on those headers… sigh. this is a killer


3 months ago

not off the top of my head, this wouldn't be super related to Railway fwiw


willkesslerHOBBY

3 months ago

i've been on railway for over a year and love it and tell all my colleagues to use it. launching my startup on it this week as well. but… this one's got me really stumped


3 months ago

just gotta find a way to tell sveletkit that the incoming requests are made with https, the solution wouldn't be specific to railway in anyway though


willkesslerHOBBY

3 months ago

ok, will keep digging in then


willkesslerHOBBY

3 months ago

on my last railway project i needed to implement caddy in front of my frontend. is that also needed for sveltekit then?


3 months ago

no it's not, sveletkit has its own server


willkesslerHOBBY

3 months ago

in theory, these env vars should work:

PROTOCOL_HEADER=x-forwarded-proto
HOST_HEADER=x-forwarded-host

according to

https://svelte.dev/docs/kit/adapter-node#Environment-variables-ORIGIN-PROTOCOLHEADER-HOSTHEADER-and-PORT_HEADER

did you say that at least the protocol header was being set by railway?


willkesslerHOBBY

3 months ago

@Brody ☝️


willkesslerHOBBY

3 months ago

(they don't help)


3 months ago

yes it is, see here -


willkesslerHOBBY

3 months ago

when you set an env var is it injected into the environment at startup like

ORIGIN=[https://something](https://something) vite dev --host --port 8080


3 months ago

hold on, you are running a dev server on railway?


willkesslerHOBBY

3 months ago

only staging. does that matter?


3 months ago

only run dev servers locally, never on cloud


willkesslerHOBBY

3 months ago

so if i switch it to NODE_ENV=production, with ORIGIN set to the https domain, are you saying csrf should work?


3 months ago

you need to build and run the node-adapter


willkesslerHOBBY

3 months ago

hmm. that is in my svelte.config.js .


3 months ago


willkesslerHOBBY

3 months ago

import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),

kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
protocolheader: 'X-Forwarded-Proto', hostheader: 'X-Forwarded-Host',
}),
csrf: {
checkOrigin: true
},
alias: {
'@regulis/common': '../common/dist',
$lib: './src/lib',
$serverLib: './src/lib'
}
}
};

export default config;


3 months ago

yes, but you are likely still telling it to run dev


3 months ago

this starts a dev server, not the node-adapter


willkesslerHOBBY

3 months ago

so… how does that interact with the csrf check?


3 months ago

dont know, but best to start by running your app correctly


willkesslerHOBBY

3 months ago

i'll try with ORIGIN set to the https url and with NODE_ENV=production and see if there's any diff.


willkesslerHOBBY

3 months ago

part of the challenge here is that debugging a production app is quite difficult since logs are suppressed


3 months ago

please run a production server as shown in my links


3 months ago

it doesn't matter what you set NODE_ENV to if you are still running vite dev


willkesslerHOBBY

3 months ago

i'm doing it. the only diff i have in my package script is for start
PORT=8080 node build


willkesslerHOBBY

3 months ago

instead of node build/index.js not sure if index.js is strictly required or not


3 months ago

what command is being ran as the start command for your railway services


willkesslerHOBBY

3 months ago

startCommand = "cd packages/$RAILWAY_SERVICE && pnpm start"


willkesslerHOBBY

3 months ago

i have a monorepo so i define RAILWAY_SERVICE depending on which service i'm spinning up


3 months ago

so you aren't running a dev server on railway?


willkesslerHOBBY

3 months ago

well, the package.json actually has a fork on NODE_ENV
"start": "if [ \"$NODE_ENV\" = \"development\" ]; then pnpm dev; else PORT=8080 node build; fi"

i know, kind of janky. i was trying to make it possible to run dev on staging in railway to maek things easier to debug. maybe shot myself in the foot here!


3 months ago

just have it run the index file, no need to complicate


willkesslerHOBBY

3 months ago

HALLELUJAH! It is working now! YOu were right about production mode. damn.


willkesslerHOBBY

3 months ago

for anybody coming to this thread in the future, here are my takeaways for sveltekit on Railway

  • Always use NODE_ENV=production on railway

  • make sure to set your ORIGIN header to your frontend's domain as either set by you or railway

  • turn on csrf in your svelte.config.ts with these additional adapter headers:

  kit: {
    // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
    // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
    // See https://kit.svelte.dev/docs/adapters for more information about adapters.
    adapter: adapter({
      protocol_header: 'X-Forwarded-Proto',
      host_header: 'X-Forwarded-Host',
    }),
    csrf: {
      checkOrigin: true
    },

Possibly, include these additional headers (maybe not)
```
PROTOCOLHEADER="x-forwarded-proto" HOSTHEADER="x-forwarded-host"
PORT_HEADER="x-forwarded-port"


3 months ago

awesome!


willkesslerHOBBY

3 months ago

thank you SO much for all your help today. if i had any hair left on my skull it'd be gone by now


3 months ago

hey as a support person, dev servers are the bane of my existence


3 months ago

vercel might monkey patch your project to make sure it's always running a production server, but railway only runs what you tell it to


3 months ago

!s


Status changed to Solved brody 3 months ago