DEV Community

Brewhouse Digital
Brewhouse Digital

Posted on

Secure Authentication in Svelte using Hooks

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:

├─ src
│  ├─ routes
|  |  ├─ admin (all authenticated pages)
|  |  |  ├─ +page.svelte (main authenticated page)
|  |  |  ├─ +layout.server.js
│  │  ├─ +page.svelte (unauthenticated homepage)
│  ├─ hooks.server.js
Enter fullscreen mode Exit fullscreen mode
// /src/routes/admin/+layout.server.js
export async function load() {}
Enter fullscreen mode Exit fullscreen mode

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.

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

  return response;
}
Enter fullscreen mode Exit fullscreen mode

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.

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.

// 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;
}
Enter fullscreen mode Exit fullscreen mode

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

// 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";
}
Enter fullscreen mode Exit fullscreen mode

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

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";
}
Enter fullscreen mode Exit fullscreen mode

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 for their tutorial on this. Their version is in Typescript and mine is in Javascript.

Top comments (1)