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.
ioredis
// Install in your project root
pnpm i -D ioredis
@ethercorps/sveltekit-redis-session
pnpm i -D @ethercorps/sveltekit-redis-session
This package made with
ioredis
as primary redis-client for NodeJS which supports async/await.
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.
lib
directory with the name sever
and then add a file sessionManager.ts
server
directory insidelib
have special meaning in SvelteKit. Any file insideserver
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)
SECRET
andREDIS_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 thisredis://username:password@host:port
and if your are running redis on your systemREDIS_URL
may look likeredis://localhost:6379
.
// 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.
// 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
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:
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.
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.
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.
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.
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
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
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
}
});
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 !== '[email protected]' ||
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.
+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);
};
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.
+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;
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 ❤️.
Our service will help you design and develop your product.