Secure Authentication in Svelte using Hooks
Tags: svelte, authentication, javascript, tutorial
Published: 2023-03-19 17:35:50

If you need to block access to a particular part of your app, most new Svelte developers (including myself) immediately jump into the `+layout.svelte` file to start handling the requests. This unfortunately causes some side effects that are pretty difficult to fix. The way around this is to use the magic `hooks.server.js` file instead!

The `hooks.server.js` file intercepts all _dynamic_ routes before rendering them. This is an important distinction because prerendered pages will not flow through this process (which could lead to a lot of frustrating debugging). The easiest way around this is to create a blank `+layout.server.js` file in your `admin` routes folder. An example might look like this:

```bash
├─ src
│  ├─ routes
|  |  ├─ admin (all authenticated pages)
|  |  |  ├─ +page.svelte (main authenticated page)
|  |  |  ├─ +layout.server.js
│  │  ├─ +page.svelte (unauthenticated homepage)
│  ├─ hooks.server.js
```

```js
// /src/routes/admin/+layout.server.js
export async function load() {}
```

The magic here is that now every page within your `admin` folder won't be prerendered. This forces any requests in that directory to flow through the `hooks.server.js` file.

Now that we have that setup, let's take a look at the `hooks.server.js` file.

The first thing we need to do is setup the `handle()` function. This will intercept all requests and this is where we can do our checks.

```js
// hooks.server.js
export const handle = async({event, resolve}) => {
  const response = await resolve(event);

  return response;
}
```

Before we continue, lets look at the two properties being passed through the `handle()` function: `event` and `resolve`.

Event contains all the information related to the file request. This includes user's cookies, the browser http headers, and the URL of the specific request. You can read more indepth docs here: [SvelteKit Hooks](https://kit.svelte.dev/docs/hooks).

The second item, `resolve`, is the function that creates the finished HTML. This will be useful in a later tutorial when we modify the theme value from light to dark, based on the user's cookies. For now, we're going to focus on the authentication piece.

Inside our `handle()` function is the `await resolve(event)` call. This is a SvelteKit thing that essentially tells the server that it is ready to build the HTML before sending it to the client. Returning that `response` value renders the page as normal.

## Authentication Setup

In our `hooks.server.js` file, we can get the current requested pathname, and check if the user is trying to access our admin routes or not. We can also get the cookies to check our auth token as well. In this example, we'll just use an example auth token, but you can swap this out for any authentication library SDK that you'd like to use.

```js
// hooks.server.js
export const handle = async({event, resolve}) => {
  const requestedPath = event.url.pathname;
  const cookies = event.cookies;

  // Auth check will go here

  const response = await resolve(event);

  return response;
}
```

Now that we have the URL and the cookies, we can do the validation.

```js
// hooks.server.js
export const handle = async({event, resolve}) => {
  const requestedPath = event.url.pathname;
  const cookies = event.cookies;

  // Auth check
  const isTokenValid = validateTokenFunction(cookies);

  const response = await resolve(event);

  return response;
}

const validateTokenFunction = (cookies) => {
  // This will look for the user's cookies and see if the auth token exists
  const currentToken = cookies.get("auth-token");

  // If the auth token is valid (i.e. matches the string 123), then it will return `true`.
  return currentToken === "123";
}
```

Now that we know if our auth token is valid, we can compare it against the current route like so:

```js
import {redirect} from '@sveltejs/kit';

// hooks.server.js
export const handle = async({event, resolve}) => {
  const requestedPath = event.url.pathname;
  const cookies = event.cookies;

  // Auth check
  const isTokenValid = validateTokenFunction(cookies);

  // Restrict all routes under /admin
  if(currentPath.includes("/admin")) {
    if(!isTokenValid) {
      throw redirect(303, "/");
    }
  }

  const response = await resolve(event);

  return response;
}

const validateTokenFunction = (cookies) => {
  // This will look for the user's cookies and see if the auth token exists
  const currentToken = cookies.get("auth-token");

  // If the auth token is valid (i.e. matches the string 123), then it will return `true`.
  return currentToken === "123";
}
```

Notice we imported the svelte function `redirect`. This will allow us to send the user somewhere else _before_ the HTML is rendered. This is the most secure way to keep your admin content from prying eyes.

You can expand this `if` statement to include additional checks for user roles, or other types of requests. This way you can have an admin panel with multiple levels of user access, just by adding additional checks.

And just like that, your SvelteKit application is secured. Try it out and see how the site won't let you access the `/admin` pages without a cookie called `auth-token` set to `123`.

## Additional Improvements

You'll probably want to move that `validateToken()` function into your `/src/lib/server` folder so that the server doesn't recreate it every time a route is accessed. I usually create a file called `authentication.js` in there to house all types of auth-checking functions. 

## Sources

Shoutout to [Huntabyte: Protect SvelteKit Routes with Hooks](https://youtu.be/K1Tya6ovVOI) for their tutorial on this. Their version is in Typescript and mine is in Javascript.