Session Management in SvelteKit Using Stores with svelte-kit-sessions
Tags: svelte, sveltekit, javascript, typescript
Published: 2024-01-11 01:38:00

## Introduction

When looking for session management libraries in SvelteKit, it seemed that there were only ones that store session information in cookies or have their stores fixed to Redis (although, maybe I just didn't search well enough).

Personally, having experience with development in [Express](https://expressjs.com/ja/), I found the [express-session](https://www.npmjs.com/package/express-session) approach quite flexible, where one could freely choose the store, and session information was saved in the store. I thought it would be desirable to have a session management module for SvelteKit that could do something similar.

Thus, I developed and published a session management module for SvelteKit, named `svelte-kit-sessions`, which presupposes the use of a store. In this article, I’d like to discuss how one can implement session management in SvelteKit using this module.

https://www.npmjs.com/package/svelte-kit-sessions

## Overview of [svelte-kit-sessions](https://www.npmjs.com/package/svelte-kit-sessions)

[svelte-kit-sessions](https://www.npmjs.com/package/svelte-kit-sessions) is a module that stores the session ID (+signature) in a cookie. It uses this cookie's session ID as the key in the store to save session information. If a session exists, it retrieves the stored session information from the store, making it usable in SvelteKit's [Actions](https://kit.svelte.jp/docs/form-actions), [API routes](https://kit.svelte.jp/docs/routing#server), and [Server hooks(handle)](https://kit.svelte.jp/docs/hooks#server-hooks).

The specific usage method is described in the README's [Usage](https://www.npmjs.com/package/svelte-kit-sessions#usage) section, and it feels like the following in use:

### Actions

Authenticate the user and create a session.

```ts
// src/routes/login/+page.server.ts
import type { ServerLoad, Actions } from '@sveltejs/kit';
import db from '$lib/server/db.ts';

export const load: ServerLoad = async ({ locals }) => {
    const { session } = locals; // you can access `locals.session`
    const user = await db.getUserFromId(session.data.userId);
    return { user };
};

export const actions: Actions = {
    login: async ({ request, locals }) => {
        const { session } = locals; // you can access `locals.session`

        const data = await request.formData();
        const email = data.get('email');
        const password = data.get('password');
        const user = await db.getUser(email, password);

        await session.setData({ userId: user.id, name: user.name }); // set data to session
        await session.save(); // session save and session create (session data is stored and set-cookie)

        return { success: true };
    },
    ...
};
```

### API route

When creating a TODO for a user with a session, set the creator of the TODO to the `userId` from the session.

```ts
// src/routes/api/todo/+server.ts
import { json, type RequestEvent, type RequestHandler } from '@sveltejs/kit';
import db from '$lib/server/db.ts';

interface TodoBody {
	title: string;
	memo: string;
}

export const POST: RequestHandler = async (event: RequestEvent) => {
	const { session } = event.locals; // you can access `event.locals.session`

	const { title, memo } = (await event.request.json()) as TodoBody;
	const todoId = await db.createTodo({ title, memo, userId: session.data.userId });

	return json({ id: todoId }, { status: 200 });
};
```

### Server hooks(handle)

Redirect to the login screen in cases of access without an authenticated session.

```ts
// src/hooks.server.ts
import { redirect, type Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { sveltekitSessionHandle } from 'svelte-kit-sessions';
import RedisStore from 'svelte-kit-connect-redis';
import { Redis } from 'ioredis';

const client = new Redis({
	host: '{your redis host}',
	port: 6379
});

const checkAuthorizationHandle: Handle = async ({ event, resolve }) => {
	// `event.locals.session` is available
	if (!event.locals.session.data.userId) redirect(302, '/login');

	const result = await resolve(event);
	return result;
};

export const handle: Handle = sequence(
	sveltekitSessionHandle({ secret: 'secret', store: new RedisStore({ client }) }),
	checkAuthorizationHandle
);
```

- Reference: https://kit.svelte.dev/docs/load#redirects

## Features and Highlights of [svelte-kit-sessions](https://www.npmjs.com/package/svelte-kit-sessions)

[svelte-kit-sessions](https://www.npmjs.com/package/svelte-kit-sessions) has the following features and highlights (which might seem obvious, but are essential nonetheless):

- Ability to freely choose the store
- Flexible session configuration
  - Ability to create sessions without initialization
  - Automatic updating of session expiry (expire)
  - Regeneration of sessions to prevent session hijacking
  - Support for multiple cookie signing secrets and secret rotation 

### Ability to Freely Choose the Store

As listed in the [Compatible Session Stores](https://www.npmjs.com/package/svelte-kit-sessions#compatible-session-stores) section, the session store can be freely chosen.

Even stores not listed in [Compatible Session Stores](https://www.npmjs.com/package/svelte-kit-sessions#compatible-session-stores) can be used by implementing a class that satisfies the interface described in the [Session Store Implementation](https://www.npmjs.com/package/svelte-kit-sessions#session-store-implementation) section.

### Flexible Session Configuration

#### Ability to Create Sessions Without Initialization

Setting the [saveUninitialized](https://www.npmjs.com/package/svelte-kit-sessions#saveuninitialized) option to `true` allows svelte-kit-sessions to automatically create sessions.

This option should be set to `false` in scenarios like login where a session is issued for the first time, but it's a convenient option for scenarios where sessions are issued immediately upon access.

#### Automatic Updating of Session Expiry

Setting the [rolling](https://www.npmjs.com/package/svelte-kit-sessions#rolling) option to `true` configures the sessions to automatically extend the validity period by the time specified in the `maxAge` cookie option.

#### Regeneration of Sessions to Prevent Session Hijacking

To prevent session hijacking, it is necessary to have different sessions before and after login. This can be easily implemented using the [session.regenerate()](https://www.npmjs.com/package/svelte-kit-sessions#sessionregenerate) method.

For example, during authentication via OpenID Connect, the session that stores the `state` or `codeVerifier`, and the session after the callback (post-login) should be different. In such cases, the following implementation could be applicable (assuming an OpenID Connect flow, specifically the Authorization Code Flow, where scopes like `openid`, `profile`, etc., are specified to receive an ID token and user information).

```ts
// src/hooks.server.ts
import { redirect, type Handle } from '@sveltejs/kit';
import { sequence } from '@sveltejs/kit/hooks';
import { sveltekitSessionHandle } from 'svelte-kit-sessions';
import RedisStore from 'svelte-kit-connect-redis';
import { Redis } from 'ioredis';
import oauthClient from '$lib/server/oauth-client.js'; // Be a library for OpenID Connect (OAuth2.0)

const client = new Redis({
	host: '{your redis host}',
	port: 6379
});

const checkAuthHandle: Handle = async ({ event, resolve }) => {
	// Callback endpoints, use temporary tokens to get ID tokens, etc.
	if (event.url.pathname === '/oauth/callback' && event.request.method === 'GET') {
		if (event.locals.session.data.state !== event.params.state) throw new Error('Invalid state.');

		const data = await oauthClient.callback({
			request: event.request,
			state: event.locals.session.data.state,
			codeVerifier: event.locals.session.data.codeVerifier
		});

		const newSession = await session.regenerate();
		await newSession.setData({ userId: data.sub, email: data.email, name: data.name });
		await newSession.save();
		throw redirect(302, '/');
	}

	// Start Authorization Code Flow with no session
	if (!event.locals.session.data.userId) {
		const { authUri, state, codeVerifier } = oauthClient.start();
		await event.locals.session.setData({ state, codeVerifier });
		await event.locals.session.save();
		throw redirect(302, authUri);
	}

	const result = await resolve(event);
	return result;
};

export const handle: Handle = sequence(
	sveltekitSessionHandle({ secret: 'secret', store: new RedisStore({ client }) }),
	checkAuthHandle
);
```

**Note** The above is implemented as a sample code in `hooks.server.ts` for the Authorization Code Flow, but in reality, it should probably be properly abstracted into API Routes or similar.

#### Ability to Specify Multiple Cookie Signing Secrets and Perform Secret Rotation

The [secret](https://www.npmjs.com/package/svelte-kit-sessions#secret) option allows for not only a string but also an array of strings. This enables the periodic update of the cookie signing secret while ensuring that existing cookies do not become invalid.

Specifically, by setting the secret to something like `["secret2", "secret1"]`, you can add a new secret, `"secret2"`, without invalidating the originally used `"secret1"`. When multiple secrets are set, the secret used for signing cookies will be the first one in the array.

## Conclusion

This article summarized the features and highlights of `svelte-kit-sessions`, a session management module for SvelteKit that presupposes the use of a store.

I hope `svelte-kit-sessions` (https://www.npmjs.com/package/svelte-kit-sessions) proves to be useful for those who want to incorporate session management into their SvelteKit development. It would be great if it helps someone out there.