How to Manage Sessions in SvelteKit with SvelteKitAuth

Chapter 10 of Comprehensive Guide to SvelteKitAuth: Secure Authentication for SvelteKit Apps

We will explore two approaches to managing sessions in SvelteKit: one on the server side and the other on the client side.

Managing session on the Server side

As soon as the authentication is successful, SvelteKitAuth populates user sessions within locals that are accessible across the server-side code (i.e., hooks.server.ts, +page.server.ts, +layout.server.ts, +server.ts). Here is the programmatic representation of the statement:

src/hooks.server.ts

const { handle: getAuthConfig } = SvelteKitAuth(async (event) => {
  const config: SvelteKitAuthConfig = {...}
});
export const handle = SvelteKitAuth(getAuthConfig) satisfies Handle;
// this adds { users, expires } property to locals

In server files, i.e., src/\.server.ts*,

We can access the session using the auth() method. Here, the session object will hold the user property that contains the user information and the expires property that depicts until which session is valid.

// [page | layout].server.ts
export const load = async (event) => {
  const session = await event.locals.auth();
  const isUserLoggedIn = !isEmpty(session?.user);
  if (isUserLoggedIn) {
    // execute business logic
  } else {
    // redirect user to sign-in oage
  }
  return { session };
};

// +server.ts
export const POST = (async ({ locals }) => {
  const session = await locals.auth();
  const isUserLoggedIn = !isEmpty(session?.user);
  if (isUserLoggedIn) {
    // execute business logic
  } else {
    // redirect user to sign-in oage
  }
};

We can verify if the user is authenticated or not based on the user and expires properties.

NOTE: The expires property updates itself every time the call to auth() is made, thus extending the user session.

Lastly, the page and layout server files can return this session object so that it is available on the client side as well.

Managing session on the Client side

The session management on the client starts from the session property that was returned by parent layout or page server files. It will be available inside the $page store, in the data property: $page.data. In this case, we return an object with the session property, which is what we are accessing in the other code paths.

In the src/routes/+page.svelte.ts file, we can access the session variable:

<script>
  import { page } from '$app/stores';
</script>

{#if $page.data?.session?.user}
  <span>Display User specific Information</span>
{/if}

In universal load functions, src/routes/+[layout | page].ts, we can access the session variable:

export const load = (async ({ data }) => {
  const session = data.session;
  const isUserLoggedIn = !isEmpty(session?.user);
  if (isUserLoggedIn) {
    // execute business logic
  } else {
    // redirect user to sign-in oage
  }

  return { session };
});

This mechanism allows us to protect components from unauthorized access, i.e., Handling Authorization Per Component.

Coming to the second use case to protect routes/paths from unauthorized access, i.e., Handling Authorization Per Route. For this scenario, we first create an API route that returns the current session status.

src/routes/api/get-session-status/+server.ts

import type { RequestHandler } from "@sveltejs/kit";
import { isEmpty } from "lodash-es";

export const GET = (async (event) => {
  let returnValue = { status: 200 };
  try {
    const session = await event.locals.auth();
    const user = session?.user;
    const isUserLoggedIn = !isEmpty(session) && !isEmpty(user);
    returnValue = isUserLoggedIn ? { status: 200 } : { status: 401 };
  } catch (ex: any) {
    console.log(`Exception occured while quering user session: ${ex?.message}`);
    returnValue = { status: 401 };
  }

  return new Response(JSON.stringify(returnValue), { status: 200 });
}) satisfies RequestHandler;

Before navigating to each route, we can check if session is defined or not

<script lang="ts">
  import { beforeNavigate, goto } from '$app/navigation';

  beforeNavigate(async ({ to, cancel }) => {
    if (to?.url && to.url.pathname !== '/') {
      // check user session on every navigation
      const request = await fetch(`${window.location.origin}/api/get-session-status`);
      const response = await request.json();
      const publicURLs = ['/ssr-login', '/ssr-logout', '/public', '/'];
      if (response.status !== 200 && !publicURLs.includes(window.location.pathname)) {
        // user is not-logged in, redirect to sign-in screen
        cancel();
        goto(`/`);
      }
    }
    return true;
  });
</script>

Conclusion

In conclusion, managing sessions in SvelteKit with SvelteKitAuth can be effectively handled both on the server side and the client side. On the server side, sessions are managed using the auth() method, which provides a session object containing user information and session expiration details. This session object can be accessed across various server-side files and returned to the client side for further use. On the client side, the session data is available in the $page store, allowing for component-level and route-level authorization checks. By leveraging these mechanisms, developers can ensure secure and efficient session management in their SvelteKit applications.

Here is the link to the GitHub repository with the codebase. In the next article, we will learn how to rotate the refresh token in SvelteKitAuth.

Did you find this article valuable?

Support Aakash Goplani's Blog by becoming a sponsor. Any amount is appreciated!