Sending SvelteKit server requests with httpOnly cookies
Tags: svelte, sveltekit, docker, auth
Published: 2021-12-10 11:04:33

Form my new project I have chosen a [SvelteKit](https://kit.svelte.dev) for a frontend and [AdonisJS](https://adonisjs.com) on a backend.
On Svelte I appreciate [SSR](https://kit.svelte.dev/docs#appendix-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](https://kit.svelte.dev/docs#loading)) 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](https://github.com/sveltejs/kit/issues/1198) or [#1777](https://github.com/sveltejs/kit/issues/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](https://kit.svelte.dev/docs#hooks-handle) 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](https://kit.svelte.dev/docs#routing-endpoints) 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](https://hub.docker.com/r/jwilder/nginx-proxy) 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`:

```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
```

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.