DEV Community

yutak23
yutak23

Posted on

Session Management in SvelteKit Using Stores with svelte-kit-sessions

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, I found the 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

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, API routes, and Server hooks(handle).

The specific usage method is described in the README's Usage section, and it feels like the following in use:

Actions

Authenticate the user and create a session.

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

API route

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

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

Server hooks(handle)

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

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

Features and Highlights of svelte-kit-sessions

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 section, the session store can be freely chosen.

Even stores not listed in Compatible Session Stores can be used by implementing a class that satisfies the interface described in the Session Store Implementation section.

Flexible Session Configuration

Ability to Create Sessions Without Initialization

Setting the 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 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() 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).

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

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 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.

Top comments (1)

Collapse
 
theether0 profile image
Shivam Meena

You can also check out:
npmjs.com/package/@ethercorps/svel...
github.com/etherCorps/SK-Redis-Ses...

Supports all runtimes - Redis based sessions.