DEV Community

Cover image for Redis Integration in SvelteKit: A Game-Changer for Session Management
Shivam Meena
Shivam Meena

Posted on

Redis Integration in SvelteKit: A Game-Changer for Session Management

Introduction

As you all know SvelteKit released v1.0 last month and we all started to use it in medium size project as of now. But one pain point still exist in SvelteKit which is managing user auth and session. But it's not going to stop us from using SvelteKit. So what we can do is minimize the pain by our own magic and from there i started to make a small package which minimize that pain. Let me introduce new session management library for SvelteKit which is @ethercorps/sveltekit-redis-session and it uses Redis as session manager. We all know how fast Redis is, even Ben Awad himself says use Redis for session management far better than other things. I took that path and got better results, so i extracted it from my project and make this package.

Resources

Prerequisite

  • SvelteKit project
  • Redis client library for NodeJS: ioredis
// Install in your project root
pnpm i -D ioredis
Enter fullscreen mode Exit fullscreen mode
  • Install @ethercorps/sveltekit-redis-session
pnpm i -D @ethercorps/sveltekit-redis-session 
Enter fullscreen mode Exit fullscreen mode

This package made with ioredis as primary redis-client for NodeJS which supports async/await.

How to use

I'm using Typescript, you can use JS/TS as per your project.

After creating and installing all the dependencies we can start with our work.

Initializing Redis Session Store

  • Create a folder in lib directory with the name sever and then add a file sessionManager.ts

server directory inside lib have special meaning in SvelteKit. Any file inside server directory can only used in server side logic which will help us to not import some code to client side.

// lib/server/sessionManager.ts

import { RedisSessionStore } from '@ethercorps/sveltekit-redis-session';
import Redis from 'ioredis';
import { SECRET, REDIS_URL } from '$env/static/private';

// Now we will create new Instance for RedisSessionStore
const options = {
  redisClient: new Redis(REDIS_URL)
  secret: SECRET
} 
// These are the required options to use RedisSessionStore.
export const sessionManager = new RedisSessionStore(options)
Enter fullscreen mode Exit fullscreen mode

SECRET and REDIS_URL are supposed to be .env file.
SECRET is a key that is not supposed to be known by third party.
REDIS_URL looks like this redis://username:password@host:port and if your are running redis on your system REDIS_URL may look like redis://localhost:6379.

  • All available options for RedisSessionStore
// option default explanation

1. redisClient > No Default > Required and Accepts a Redis Client from `ioredis`.
2. secret > No Default > Required and Accepts a string which is supposed to be a secret key.
3. cookieName > Default: `session` > Its the name of cookie which will be added to browser. Accepts a string can we anything you want.
4. prefix > Default: `sk-session:` > A prefix for all the keys added to redis which helps to get all active session with the same prefix. Accept a string can we anything and make a unique prefix which does not exist in redis.
5. signed > Default: true > Signed cookies mean that every key that is added in cookie going to be signed to check if its assigned by us. Its accepts boolean value.
6. encrypted > Default: false > If you want to encrypt your cookies using `aes-256-cbc` algorithm with secret key. Which makes it more secure from tempering. It accepts boolean value.
7. useTTL > Default: true > Redis have a functionality to delete a key automatically from redis after a given time.
// Given time always going to be your maxAge of cookie more on it in cookies options.
8. renewSessionBeforeExpire > Default: false > If you want to renew user session and cookie before expiring automatically you can use it. This works with `renewBeforeSeconds`.
9. renewBeforeSeconds > Default: 30 * 60 > This will update session and cookie before 30 min if user active at that time.
It used in combination of `renewSessionBeforeExpire`. It accept number which is supposed to be seconds as default is 30 minutes.
10. serializer > Default: `JSON` > This is used to stringify data and parse back to data which is supposed to be saved in redis. It accepts 
`export type Serializer = {
    stringify: Function;
    parse: Function;
};`

11. cookiesOptions > Default: defaultCookiesOption
const defaultExpireSeconds: number = 60 * 60 * 24; // One day in seconds
const defaultCookiesOption: CookieSerializeOptions = {
    path: '/',
    httpOnly: true,
    sameSite: 'strict',
    secure: !dev,
    maxAge: defaultExpireSeconds
}; > It takes any of the parameter accepted by CookieSerializeOptions type from SvelteKit. These options are for setting cookies for the browser.

> In cookiesOptions we have a field `maxAge` which also our time to live(ttl) for redis key expiration time. maxAge accepts time in seconds and which is also in going to be key expire time in redis. // 
Enter fullscreen mode Exit fullscreen mode

These are all the options accepted by RedisSessionStore.

// CookieSerializeOptions type of SvelteKit

export interface CookieSerializeOptions {
    domain?: string | undefined;
    encode?(value: string): string;
    expires?: Date | undefined;
    httpOnly?: boolean | undefined;
    maxAge?: number | undefined;
    path?: string | undefined;
    priority?: 'low' | 'medium' | 'high' | undefined;
    sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined;
    secure?: boolean | undefined;
}

// Learn more about cookieSerializationOptions: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/cookie/index.d.ts#L14
Enter fullscreen mode Exit fullscreen mode

All available method on RedisSessionStore

RedisSessionStore have multiple methods but 6 of them are useful to user, which of them 5 are main methods and one supporting methods.

Every method returns { data: any, error: boolean, message: string }

RedisSessionStore's main methods:

  1. createNewSession => Which accepts (cookies, sessionData, key?=''). It adds a new session to Redis and set cookie to cookies method with the name 'session' or options.cookieName. cookies is cookies from requestEvent, sessionData is dict which is for data you want to save in Redis and key is optional if you don't want to set default random key generated from crypto.randomUUID function. It returns { Redis Key without prefix, cookie value } in data.

  2. getSession => Which accepts only cookies which comes from requestEvent. It first validate cookie which is based on your signed or encryption method. It also checks for renewSessionBeforeExpiry condition if valid update the key and cookie expire time to default maxAge time. It returns saved sessionData from Redis which you gave while creating new session.

  3. updateSessionExpiry => Which accepts cookies method from requestEvent. It Validate the cookies then update the expiry to session and cookie. We already handle a basic auto update based on the renewSessionBeforeExpiry option which needs active user request within renewBeforeSeconds time frame. That's why you can use this method to add your own logic when you wanna update cookies. E.g. check user activity, last api request frame and etc. It returns cookie value in data.

  4. delSession => This one also accepts cookies method from requestEvent. It validate the cookie if cookies are not valid it won't gonna delete session and cookie this will return error: true which allows you to handle what to do with invalid cookies in every method. After successful validation it's gonna return unique value that makes Redis key with prefix value.

  5. deleteCookie => It also takes cookies method from requestEvent. This method for just to delete cookie from browser without any validation. For e.g. if you got validation error in any method you can just delete cookies without removing session from Redis which is only possible with valid key. It returns nothing just gonna delete if any error it's gonna console.log it.

Other methods maybe useful for your own logic

  1. _validateCookie => It takes cookies method from requestEvent and validate cookie which is created using createNewSession method from RedisSessionStore. It return Redis key without prefix if no errors else null in data.

Using Methods in our projects Authentication.

Here I'm going to show how you can use them in API's (+server.ts) and same as it you can use in SvelteKit page actions. I'm making some assumptions like you already created user and have email and password fields, You will handle data validation for the data provided by the client.

Here is our sessionManager file's code

import { RedisSessionStore } from '@ethercorps/sveltekit-redis-session';
import Redis from 'ioredis';
import { SECRET, REDIS_URL } from '$env/static/private';
export const sessionManger = new RedisSessionStore({
    redisClient: new Redis(REDIS_URL),
    secret: SECRET,
    prefix: 'redisk-example:', // redis key prefix
    cookieName: 'session', // cookie name in browser
    cookiesOptions: {
        maxAge: 10 * 60 // ten minutes
    }
});
Enter fullscreen mode Exit fullscreen mode
  • First example shows how you can use createNewSession to login a user in login/+server.ts
// Github: examples/src/routes/api/login/+server.ts

import { fail, json, redirect } from "@sveltejs/kit";
import type { RequestHandler } from './$types';
import { sessionManger } from '$lib/session';

export const POST = (async ({ request, locals, cookies }) => {

  const { email, password } = await request.json();
  if (
    email !== 'shivam@example.com' ||
    password !== 'Shivam@Meena'
  ) {
    return fail(400, {
      data: { email, password },
      message: 'Invalid credentials'
    });
  } // user validation

/* get user data after validation */
  const { data, error, message } = await sessionManger.createNewSession(cookies, {
    email
  }); // creating new session
  if (error) {
    console.log(message);
    return fail(400, {
      data: { email, password },
      message
    });
  } // error check while creating session
  return json({ success: true, message}); // no error return
}) satisfies RequestHandler;
Enter fullscreen mode Exit fullscreen mode

In above example you can see we just passed our cookie from requestEvent and user sessionData for session to createNewSession in Redis and adding cookie to request.

  • This example shows how you can use getSession in +hooks.server.ts
// Github: examples/src/+hooks.server.ts

import type { Handle } from '@sveltejs/kit';
import { sessionManger } from '$lib/session';
import { redirect } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
    const userSession = await sessionManger.getSession(await event.cookies);
    event.locals = {
        isUserLoggedIn: false,
        user: null
    };
    if (userSession.error) {
        console.log(userSession);
        await sessionManger.deleteCookie(await event.cookies);
        return resolve(event);
    }
    if (userSession && userSession.data) {
        event.locals = {
            isUserLoggedIn: true,
            user: { email: userSession?.data?.email as string }
        };
    }
    return resolve(event);
};
Enter fullscreen mode Exit fullscreen mode

In hooks.server.ts we will get our session against our cookies and it's going to return sessionData inside userSession.data and error i will delete the cookie.

  • This example shows how you can use delSession method in +logout/server.ts
// Github: examples/src/routes/api/logout/+server.ts

import { json, redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { sessionManger } from '$lib/session';

export const POST = (async ({ request, locals, cookies }) => {
    if (locals && locals.isUserLoggedIn) {
        const { email } = await request.json();
        const deleteData = await sessionManger.delSession(cookies);
        if (deleteData.error) await sessionManger.deleteCookie(cookies);
        return json({ loggedIn: false, message: 'Successfully logged out' });
    }
    throw redirect(302, '/');
}) satisfies RequestHandler;
Enter fullscreen mode Exit fullscreen mode

Here we just passed our cookies and checked for error if found error deleted the cookies applied to request.

In above examples you have seen how we can utilize createNewSession, getSession, delSession and deleteCookie. You can try updateSessionExpiry and _validateCookie in your project and let me know in comments what you did and your logic this will be very helpful for examples.


If anyone wanna suggest and contribute please check official repo for the project.


This is me writing for you. If you wanna ask or suggest anything please put it in comment and show some love ❤️.

Top comments (1)

Collapse
 
gevera profile image
Denis Donici

Great stuff. Thanks for sharing, Shivam.