SSR Authentication guide for SvelteKit

SSR Authentication guide for SvelteKit

In this post, I will write about how to guard your pages and endpoints and how to authenticate easily with SSR.

Disclaimer: SvelteKit is still in beta; it could change a lot before the first official release. I will try to keep this post updated as much as I could

Here is the example repository for all the things I have written below. (+ it includes TailwindCSS and node adapter configuration)

Let's clear out some terms.

What is SvelteKit?

If you are familiar with NextJS, or NuxtJS, then you will know what SvelteKit is. It is a framework over Svelte, which helps you to do a lot of things behind the scenes, like:

  • SSR and client-side hydration
  • Prerendering pages
  • Endpoints (API endpoints in the same codebase)
  • Etc...

It has a very awesome and straightforward documentation. Worth reading it!

That’s why I do not want to go deep into the building blocks of SvelteKit. I want to create a guide on some advanced things which are not written in the docs.

What is SSR / SPA / client-side hydration?

SSR is an abbreviation of Server Side Rendering. That means the HTML page is rendered on a server, and the raw, rendered HTML is sent to the browser which renders it.

I have to point out that it is not a new technology. Actually, the first web applications were server-side rendered (like PHP applications). The hype around it just came back into the tech world a few years ago, after realizing that SPA’s have many cons (and a lot of pros, of course).

SPA is an abbreviation of Single Page Application. That means the server is only sending once a simple skeleton HTML with a javascript file inside. That javascript file is downloaded and executed immediately in the browser and builds the HTML DOM dynamically. As the rendering speed depends on the user’s device, the user experience could be very different.

Which is better?

None. Both have their pros/cons and use cases. No absolute winner here.

What is client-side hydration?

In this case, SvelteKit renders the HTML DOM on the server (SSR), sends it to the user’s browser, where the browser takes over the execution (client-side hydration). After this point, your application behaves as a SPA. Cool, right?

Why it's harder to do the authentication in SSR than SPA?

As the first request is always executed on the server, where there is no browser environment/functions available, it's not straightforward for most front-end developers to handle it - or at least it was not clear for me for a while. 😁

Let's start coding

But, where should I start?

If you use SvelteKit's SSR with client-side hydration, you need to check whether the user is logged in in two parts of your application, in the backend side and the frontend side.

The most important thing to remember is: there is no localStorage on the server-side. So if you would like to store a JWT token in localStorage and use that for validating the user, it won't work. We will use cookies.

Backend side (API endpoints)

SvelteKit has a special file called hooks. It exports two functions, a handle and a getSession, which are executed on all server-side requests. So it's a perfect place to validate the user!

Handle

The handle function runs only on the server-side, so anything used inside it won't be visible to the client/browser.

This is where you need to:

  1. Parse the cookies sent with each request by the browser.
  2. Check whether the token is valid (do not use the verifyToken function in a real-world application😅).
  3. Set the request.locals object with the correct data you would like to pass to the frontend. This will be important for the next step! You could also add authorization details to it as well.
import * as cookie from 'cookie'
// Pages allowed to visit without authentication
const publicPages = ['/', '/api'] 

# Could be an async function. No limitation here.
function verifyToken(token) {
    if (token === 'haha') {
        return true
    }
    return false
}

export async function handle({ request, render }) {
    const { token } = cookie.parse(request.headers.cookie || '');
    if (token) {
        request.locals.isLoggedIn = verifyToken(token)
    } else {
        request.locals.isLoggedIn = false
    }
    const response = await render(request);
    if (!request.locals.isLoggedIn && !publicPages.includes(request.path)) {
     // If you are not logged in and you are not on a public page, 
     // it just redirects you to the main page, which is / in this case. 
        return {
            status: 301,
            headers: {
                location: '/'
            }
        };
    }
    return {
        ...response
    }
}

getSession

This function returns the session object, which will be accessible on the frontend. You should only return data that is safe to expose for everyone!

Pretty straightforward.

export function getSession(request) {
    const { isLoggedIn } = request.locals;
    return {
        isLoggedIn
    };
}

After this point, all endpoints (except /api) are protected by the token and the verifyToken function. 🎉🥳

Frontend side

The frontend side is way simpler than the backend.

You should have a root level __layout.svelte file (src/routes/__layout.svelte), used for ALL pages and components.

In SvelteKit, you could have a function called load in pages and components, which runs before a component is created. So it's a perfect place to determine whether the user is logged in or not!

Remember when I said the first request is always executed on the server-side? That means that the getSession function is always executed first, so the session is already set when you get to the point where you load any svelte components. Taking advantage of this, we need to check the session in the load function of the root __layout.svelte file.

<script context="module" lang="ts">
    const publicPages = ['/', '/api'] ;
    /**
     * @type {import('@sveltejs/kit').Load}
     */
    export async function load(session) {
        const { path } = session.page;
        if (publicPages.includes(path)) {
            return {};
        }

        if (!session.session.isLoggedIn) {
            return {
                status: 301,
                redirect: '/'
            };
        }
        return {};
    }
</script>

And that's all! Only authenticated users could get the pages and endpoints which are not public.

Conclusion

SvelteKit is an up-and-coming framework. I bet it will become huge if it isn't replaced by another framework (just like it replaces Sapper). I did not cover all the things that SvelteKit could do, so it is worth reading it or watching Rich Harris latest video about it:


If you are interested in my journey, be sure to follow me on Twitter or here.


Did you find this article valuable?

Support Andras Bacsai by becoming a sponsor. Any amount is appreciated!