svelte-kit-sessions
v0.4.0
Published
User-friendly session management module for SvelteKit. Effortlessly integrate efficient session handling into your projects. Session is stored in the store (ex. Redis, Cloudflare KV, etc.), not in a cookie.
Downloads
705
Maintainers
Keywords
Readme
svelte-kit-sessions
svelte-kit-sessions is user-friendly session management module for SvelteKit. Effortlessly integrate efficient session handling into your projects.
Session is stored in the store (ex. Redis, Cloudflare KV, etc.), not in a cookie.
Features
- Easy Session Management:
Usesvelte-kit-sessions
for various scenarios, including authentication and issuing sessions with OpenID Connect. It's flexible, allowing user info storage in sessions or using JWTs. - Customizable Store:
Choose the ideal storage for your needs. Options range from the default MemoryStore to Redis and Cloudflare KV. - Edge Environment Support:
svelte-kit-sessions
is compatible with Edge environments like Cloudflare Pages Functions (Cloudflare Workers).
Enhance your SvelteKit development with svelte-kit-sessions, the ideal solution for modern web apps.
Installation
$ npm i svelte-kit-sessions
$ yarn add svelte-kit-sessions
$ pnpm add svelte-kit-sessions
Usage
svelte-kit-sessions
stores session data in a store, so a store is need (Store is optional and defaults to MemoryStore if omitted). You can find a list of compatible stores at Compatible Session Stores.
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
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
});
export const handle: Handle = sveltekitSessionHandle({
secret: 'secret',
store: new RedisStore({ client }) // other compatible stores are available
});
or if you want to use it with your own handle, you can use sequence.
// src/hooks.server.ts
import 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 yourOwnHandle: Handle = async ({ event, resolve }) => {
// `event.locals.session` is available
// your code here
const result = await resolve(event);
return result;
};
export const handle: Handle = sequence(
sveltekitSessionHandle({ secret: 'secret', store: new RedisStore({ client }) }),
yourOwnHandle
);
After the above implementation, you can use the following in Actions, API routes and Server hooks(handle).
Actions
For example, "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 saveand session create(session data is stored and set-cookie)
return { success: true };
},
...
};
API route
For example, "when creating a TODO with a user who has a session, create the TODO with the session's userId as the creator of the TODO".
// 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)
For example, "redirect to login if access does not have a session after authentication".
// 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) throw redirect(302, '/login');
const result = await resolve(event);
return result;
};
// make sure to set sveltekitSessionHandle first
export const handle: Handle = sequence(
sveltekitSessionHandle({ secret: 'secret', store: new RedisStore({ client }) }),
checkAuthorizationHandle
);
Note The below is a sample code implementation of Authorization Code Flow in hooks.server.ts
, but in practice, it should be cut out properly in API Rotes, etc.
// 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
);
Typing your session data
You can use declaration merging to define types as follows.
// src/hooks.server.ts
declare module 'svelte-kit-sessions' {
interface SessionData {
userId: string;
name: string;
}
}
JavaScript
// src/hooks.server.js
import { sveltekitSessionHandle } from 'svelte-kit-sessions';
export const handle = sveltekitSessionHandle({ secret: 'secret' });
or if you want to use it with your own handle, you can use sequence.
// src/hooks.server.js
import { sequence } from '@sveltejs/kit/hooks';
import { sveltekitSessionHandle } from 'svelte-kit-sessions';
const yourOwnHandle = async ({ event, resolve }) => {
// `event.locals.session` is available
// your code here
const result = await resolve(event);
return result;
};
export const handle = sequence(sveltekitSessionHandle({ secret: 'secret' }), yourOwnHandle);
API
import { sveltekitSessionHandle } from 'svelte-kit-sessions';
sveltekitSessionHandle(options);
sveltekitSessionHandle(options)
Create a server hooks handle with the given options
. This allows access to event.locals.session
in hooks handles, Actions and API route.
Note Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.
Warning: The default MemoryStore
for server-side sessions is not suitable for production. It tends to leak memory, can't scale beyond a single process, and is only for debugging and development. For production-ready stores, see the list of Compatible Session Stores.
Apis(class methods)
A summary of the event.locals.session
class methods is as follows.
| Name | Arguments | Return | Description | | ---------- | -------------------------------------------------- | --------------- | -------------------------------------------------------- | | setData | 1. data (SessionData) | Promise<void> | Set data in the session. | | save | nothing | Promise<void> | Save the session (save session to store) and set cookie. | | regenerate | nothing | Promise<void> | Regenerate the session simply invoke the method. | | destroy | nothing | Promise<void> | Destroy the session. |
session.setData(data)
Set data in the session.
Note If saveUninitialized
is true
, the session is saved without calling save()
.
Conversely, if saveUninitialized
is false
, call save()
to explicitly save the session.
arguments
- SessionData
Data to be stored in the session.
In TypeScript, you can declare additional properties on your session object using declaration merging for interfaceSessionData
.
return
Promise<void>
session.save()
Save the session (save session to store) and set cookie.
arguments
nothing
return
Promise<void>
session.regenerate()
Regenerate the session simply invoke the method. Once complete, a new Session and Session
instance will be initialized.
arguments
nothing
return
Promise<void>
session.destroy()
Destroy the session.
arguments
nothing
return
Promise<void>
Property(class fields)
A summary of the event.locals.session
class fields is as follows.
| Name | Type | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| id | string | Session ID. |
| cookieName | string | Session cookie name. The value of options.name
. |
| cookie | CookieSerializeOptions & { path: string } | Session cookie options. The value of options.cookie
. |
| data | SessionData | Session data. Data stored in the session can be referenced from this property. |
| store | Store | Session store instance. If you want to manipulate the store directly, you can use this store property. |
Options
A summary of the options
is as follows.
| Name | Type | required/optional | Description |
| ----------------- | --------------------------------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| name | string | optional | The name of the session ID cookie to set in the response. The default value is connect.sid
. |
| cookie | CookieSerializeOptions | optional | Cookie settings object. See link for details. |
| rolling | boolean | optional | Force the session identifier cookie to be set on every response. The default value is false
. If cookie.maxAge
is not set, this option is ignored. |
| store | Store | optional | The session store instance. The default value is MemoryStore instance. |
| secret | string | string[] | required | This is the secret used to sign the session cookie. |
| saveUninitialized | boolean | optional | Forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified. The default value is false
. |
name
The name of the session ID cookie to set in the response (and read from in the request). The default value is connect.sid
.
Note If you have multiple apps running on the same hostname (this is just the name, i.e. localhost
or 127.0.0.1
; different schemes and ports do not name a different hostname), then you need to separate the session cookies from each other. The simplest method is to simply set different names per app.
cookie
Cookie settings object. Exactly the same options that can be specified in cookie.serialize
of the cookie npm package.
Note The default value of the cookie matches the behavior of SvelteKit. For more details, please check https://kit.svelte.dev/docs/types#public-types-cookies. However, for the cookie.path
, it is implemented so that /
is set on the svelte-kit-sessions side.
The following are options that can be set in this object.
cookie.domain
Specifies the value for the Domain
Set-Cookie
attribute. By default, no domain is set, and most clients will consider the cookie to apply to only the current domain.
cookie.encode
Specifies a function that will be used to encode a cookie's value. Since value of a cookie has a limited character set (and must be a simple string), this function can be used to encode a value into a string suited for a cookie's value.
The default function is the global encodeURIComponent
, which will encode a JavaScript string into UTF-8 byte sequences and then URL-encode any that fall outside of the cookie range.
cookie.expires
Specifies the Date
object to be the value for the Expires
Set-Cookie
attribute. By default, no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application.
Note the cookie storage model specification states that if both expires
and maxAge
are set, then maxAge
takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time.
cookie.httpOnly
Specifies the boolean
value for the HttpOnly
Set-Cookie
attribute. When truthy, the HttpOnly
attribute is set, otherwise it is not. By default, the HttpOnly
attribute is not set.
Note be careful when setting this to true
, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie
.
cookie.maxAge
Specifies the number
(in seconds) to be the value for the Max-Age
Set-Cookie
attribute. The given number will be converted to an integer by rounding down. By default, no maximum age is set.
Note the cookie storage model specification states that if both expires
and maxAge
are set, then maxAge
takes precedence, but it is possible not all clients by obey this, so if both are set, they should point to the same date and time.
cookie.partitioned
Specifies the boolean
value for the Partitioned
Set-Cookie
attribute. When truthy, the Partitioned
attribute is set, otherwise it is not. By default, the Partitioned
attribute is not set.
Note This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
More information about can be found in the proposal.
cookie.path
Specifies the value for the Path
Set-Cookie
attribute. By default, the path is considered the "default path".
cookie.priority
Specifies the string
to be the value for the Priority
Set-Cookie
attribute.
'low'
will set thePriority
attribute toLow
.'medium'
will set thePriority
attribute toMedium
, the default priority when not set.'high'
will set thePriority
attribute toHigh
.
More information about the different priority levels can be found in the specification.
Note This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
cookie.sameSite
Specifies the boolean
or string
to be the value for the SameSite
Set-Cookie
attribute.
true
will set theSameSite
attribute toStrict
for strict same site enforcement.false
will not set theSameSite
attribute.'lax'
will set theSameSite
attribute toLax
for lax same site enforcement.'none'
will set theSameSite
attribute toNone
for an explicit cross-site cookie.'strict'
will set theSameSite
attribute toStrict
for strict same site enforcement.
More information about the different enforcement levels can be found in the specification.
Note This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it.
cookie.secure
Specifies the boolean
value for the Secure
Set-Cookie
attribute. When truthy, the Secure
attribute is set, otherwise it is not. By default, the Secure
attribute is not set.
note be careful when setting this to true
, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection.
rolling
Force the session identifier cookie to be set on every response. The expiration is reset to the original maxAge
, resetting the expiration countdown. The default value is false
. If cookie.maxAge
is not set, this option is ignored.
With this enabled, the session identifier cookie will expire in maxAge
since the last response was sent instead of in maxAge
since the session was last modified by the server.
This is typically used in conjuction with short, non-session-length maxAge
values to provide a quick timeout of the session data
with reduced potential of it occurring during on going server interactions.
Note When this option is set to true
but the saveUninitialized
option is set to false
, the cookie will not be set on a response with an uninitialized session.
This option only modifies the behavior when an existing session was loaded for the request.
store
The session store instance. The default value is MemoryStore
instance.
Note See the chapter Session Store Implementation for more information on the store.
secret
This is the secret for signing session ID cookies. It can be a string or an array string of secrets.
For signing, only the first secret in the array is used; for verification, all secrets are considered.
The secret should be a complex, random string, not easily guessed.
Best practices include:
- Storing the secret in environment variables, not in your repository
- Regularly updating the secret and keeping old ones in the array
To change the secret without invalidating existing sessions, add the new secret as the first element in the array and include the old ones after it.
saveUninitialized
Forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified. The default value is false
.
Choosing false
is useful for implementing login sessions, reducing server storage usage, or complying with laws that require permission before setting a cookie. Choosing false
will also help with race conditions where a client makes multiple parallel requests without a session.
Compatible Session Stores
- svelte-kit-connect-redis A Redis based session store. Note: Not available in the Edge environment (At least in Cloudflare Workers).
- svelte-kit-connect-upstash-redis A Upstash Redis based session store. This can also be used in edge environments such as Cloudflare Pages Functions(Cloudflare Workers).
- svelte-kit-connect-cloudflare-kv A Cloudflare Workers KV based session store. This only can be used in Cloudflare Pages Functions(Cloudflare Workers).
You can implement your own store by referring to the chapter Session Store Implementation.
Session Store Implementation
Every session store must be implement specific methods.
| method | Arguments | Description | | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | get | 1. id (string) : session ID | Returns JSON data stored in the store. | | set | 1. id (string) : session ID 2. storeData (SessionStoreData) : JSON data to be stored in the store 3. ttl (number) : ttl milliseconds calculated from cookie options expires, maxAge(if neither is set, the ttl value passed will be Infinity) | Stores JSON data in the store. | | destroy | 1. id (string) : session ID | Deletes a session from the store. | | touch | 1. id (string) : session ID 2. ttl (number) : ttl milliseconds calculated from cookie options expires, maxAge(if neither is set, the ttl value passed will be Infinity) | Update expiration with ttl. |
/**
* Session store interface.
* When implementing a custom store, implement it so that it has the following methods.
*
* MemoryStore would be helpful.
* @see MemoryStore
*/
export interface Store {
/**
* Returns JSON data stored in the store.
* @param id The session ID
* @returns JSON data stored in the store
*/
get(id: string): Promise<SessionStoreData | null>;
/**
* Stores JSON data in the store.
* @param id The session ID
* @param storeData JSON data to store
* @param ttl Time to live in milliseconds. This ttl is calculated with a priority of maxAge > expires,
* which is useful for store implementation. If no maxAge and expires, ttl is *Infinity*.
* But can also be calculated independently in the store by referring to the `storeData.cookie`.
*
* @returns Promise fulfilled with undefined
*/
set(id: string, storeData: SessionStoreData, ttl: number): Promise<void>;
/**
* Deletes a session from the store.
* @param id The session ID
* @returns Promise fulfilled with undefined
*/
destroy(id: string): Promise<void>;
/**
* Update expiration with ttl.
* @param id The session ID
* @param ttl Time to live in milliseconds.
* @returns Promise fulfilled with undefined
*/
touch(id: string, ttl: number): Promise<void>;
}
For an example implementation view the MemoryStore.
Contributing
We're open to all community contributions! If you'd like to contribute in any way, please first read our Contributing Guide.