Redis Integration in SvelteKit: A Game-Changer for Session Management
Tags: webdev, redis, svelte, sveltekit
Published: 2023-01-19 13:13:29

## 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
- [Source Code - Github](https://github.com/etherCorps/SK-Redis-SessionManager)
- [Demo for Session Manager](https://sveltekit-redis-session-manager.vercel.app/)
- [Example Code for Demo](https://github.com/etherCorps/SK-Redis-SessionManager/tree/master/examples)

## Prerequisite

- SvelteKit project 
- Redis client library for NodeJS: `ioredis`
```bash
// Install in your project root
pnpm i -D ioredis
```
- Install `@ethercorps/sveltekit-redis-session` 
```bash
pnpm i -D @ethercorps/sveltekit-redis-session 
```

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

```ts
// 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)
```

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

```ts
// 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. // 
```
These are all the options accepted by RedisSessionStore.
```ts
// 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
```

### 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
```ts
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
	}
});
```
- First example shows how you can use createNewSession to login a user in `login/+server.ts`

```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;
```

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

```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);
};
```
> 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`

```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;
```

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