DEV Community

Cover image for Sending SvelteKit server requests with httpOnly cookies
Jiří Procházka
Jiří Procházka

Posted on

Sending SvelteKit server requests with httpOnly cookies

Form my new project I have chosen a SvelteKit for a frontend and AdonisJS on a backend.
On Svelte I appreciate SSR and almost boilerplate-less pure javascript code.

httpOnly cookie problem

Here and there I get stuck. Like last time, I was writing an authentication using tokens and httpOnly cookies (will write a dedicated article about it in the near future). Basically the backend is instantiating a httpOnly cookie with a session id in it and send it to the frontend. Frontend should send such cookie back with each request and the backend can read the token from the cookies and verify it.

So far, so good, unless you use the load method (loading in docs) and the request is sent not by the client, but by the SvelteKit server in pre-render. In such case the cookies must be passed to the SvelteKit server first, in order to be sent with the request to the API.

There are some discussions about it like #1198 or #1777, but the proposed solutions are just workarounds, some even with security issues.

Proposed solutions (working, but not ideal)

Basically there are proposed solutions:

1) use handle hook and read the token from cookies in the backend (handle method is run on every request before the component is created), pass it to the frontend to the load method and use it with the fetch from there. The thing is you are exposing the token to the client and it is a big security vulnerability.
It is even stated in the docs:

In the load method you:

should not directly reference any API keys or secrets, which will be exposed to the client, but instead call an endpoint that uses any required secret

2) Which is leading to the second workaround - use the SvelteKit endpoint as a proxy (do not mistake it with the backend API endpoint. This endpoint is part of your SvelteKit frontend application). To pass every request to the endpoint first, enrich the request with the cookies or token in headers and then, finally, make a request to the backend api. You can even make such endpoint generic (and proxy all your app request through this single endpoint), but it could possibly be kind of a bottleneck and I just simply don't want this proxy in my frontend app.

A proper solution

I was still hoping for a better solution, the correct one.

In SvelteKit documentation there is stated a small discreet note:

Cookies will only be passed through if the target host is the same as the SvelteKit application or a more specific subdomain of it.

Which means we have to get rid of localhost with port (localhost:####) urls and use proper domains. Locally.

I'm using Docker for a local development anyway, so some reverse proxy image was an easy choise.

The idea was simple - according to the SvelteKit docs quote above the frontend and the backend must be run on the same domain (not possible), or the frontend can run on a domain dev.localhost and the api on a subdomain be.dev.localhost.

It is also a premise for cookies to be exchanged between the frontend and the backend (the backend and frontend must share the same 2nd level domain).

If you are just keen for the solution, here is the docker-compose.yml:

version: "3.4"
services:
  proxy:
    container_name: proxy.dev.localhost
    image: jwilder/nginx-proxy
    network_mode: bridge
    ports:
      - "80:80"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
    networks:
      - myNetwork

  frontend:
    container_name: dev.localhost
    image: node:lts
    working_dir: /var/www/html/app/
    entrypoint: /bin/bash
    environment:
      - HOST=0.0.0.0
      - VIRTUAL_HOST=dev.localhost
      - VIRTUAL_PORT=80
    expose:
      - 80
      - 10000
      - 35729
    volumes:
      - ./frontend/:/var/www/html/app
    tty: true
    networks:
      - myNetwork

  backend:
    container_name: be.dev.localhost
    image: node:lts
    working_dir: /var/www/html/app/
    entrypoint: /bin/bash
    environment:
      - VIRTUAL_HOST=be.dev.localhost
    expose:
      - 80
    volumes:
      - ./backend/:/var/www/html/app
    tty: true
    networks:
      - myNetwork

  database:
    container_name: db.dev.localhost
    image: mongo:4.4.10
    environment:
      - VIRTUAL_HOST=db.dev.localhost
    ports:
      - 27017:27017
    volumes:
      - mongodata:/data/db
    networks:
      - myNetwork

networks:
  myNetwork:
    external: true

volumes:
  mongodata:
    external: true
Enter fullscreen mode Exit fullscreen mode

Important to note is:

You must also put these domains to the HOSTS file:

127.0.0.1 dev.localhost be.dev.localhost db.dev.localhost

On Windows 10 the hosts file is on the path C:\Windows\System32\drivers\etc\hosts.

Conclusion

There is a way how to setup the dev environment using Docker to simulate a production domain setup on local computer. In such setup you can use httpOnly cookies authentication without any security leaks made just because of the compromises for the local development.

Top comments (1)

Collapse
 
divstar profile image
divStar

Hi Jiri! Thank you for the article.

I am currently kinda stuck. I have some experience with Angular and React, but not with Svelte. While I believe Angular can do SSR, I have never used it. However: I do believe, that prerendered sites are great, which is why I am hesitant to turn that off.

But SSR really causes a me a lot of headaches.

I am using latest SvelteKit and creating a proper and safe login process AND handling the JWT propagation is a real choir, especially since the JWT is expected to be a HttpOnly-Cookie passed into almost every request to the server.

Sadly the docker compose option is not an option for me, because I simply dislike developing in a container (I do use docker and docker-compose for a lot of things, but development is not one of them). I wish they'd fix it somehow :/.