SvelteKit Session Management
tl;dr: Manage session cookies using hooks.server.ts
, the handle
function, event.locals
and app.d.ts
.
Overview
I've been working on a simple web app. It doesn't need self-onboarding. It doesn't need 3rd parties for user identity management. Instead, Django's model of session management fits well here:
- Authenticate via basic auth.
- On successful authentication, set a session cookie.
- When serving URLs that require authentication, check for a valid session cookie.
How does one do this in SvelteKit?
As I write, SvelteKit is still evolving quickly. A search for "sveltekit session management" turns up outdated results. For one example, StackOverflow has answers that use request.session
, but as far as I can tell that property no longer exists.
A ramble through the Svelte discord pointed me in the right direction.
hooks.server.ts
In your SvelteKit project, add a src/hooks.server.ts
file (or, if you are using JavaScript, src/hooks.server.js
). In it, define a handle
function, which starts by looking for a session cookie on the provided event
.
export const handle = async function ({ event, resolve }) { extractSessionInfo(event) // ... } satisfies Handle
Get the Cookie
This example uses a session cookie whose value is a JSON Web Token. Instead of a session ID, the token contains the name of an authenticated user.
extractSessionInfo
simply extracts the username from the session cookie and saves it to the event's local storage.
function extractSessionInfo(event: RequestEvent) { const cookie = event.cookies.get('sess_tok') const username = Tokens.sessionUsernameFromToken(cookie) if (username != null && AppDB.shared?.user.isKnownUser(username)) { event.locals.username = username } }
Here, Tokens
represents a module that manipulates JWTs. AppDB
provides a shared user
database. You'd need to write these for yourself.
event.locals
event.locals
holds state that can be shared among all the functions processing an event. You define it in src/app.d.ts
. Here, it just needs to hold an optional username:
// See https://kit.svelte.dev/docs/types#app // for information about these interfaces // and what to do when importing types declare namespace App { // interface Error {} interface Locals { username: string | undefined } // interface PageData {} // interface Platform {} }
Finish Processing
Back in the handle
function, finish processing the request by invoking the provided resolve
function.
export const handle = async function ({ event, resolve }) { extractSessionInfo(event) const response = await resolve(event) // ... } satisfies Handle
Set the Cookie
Finally, if a valid username was found, update the session cookie, set it on the response
, and return the response
.
export const handle = async function ({ event, resolve }) { extractSessionInfo(event) const response = await resolve(event) if (event.locals.username !== undefined) { Tokens.addSessionTokenForUsername(response.headers, event.locals.username) } return response } satisfies Handle
Reject Unauthenticated Requests
That's pretty much it, except for one thing: suppose handle
is processing a request that requires authentication, but a valid session cookie isn't found. In that case, handle
needs to stop processing and return an error.
export const handle = async function ({ event, resolve }) { extractSessionInfo(event) if (needsAuth(event.request)) { if (event.locals.username === undefined) { return Tokens.invalidTokenResponse() } } const response = await resolve(event) if (event.locals.username !== undefined) { Tokens.addSessionTokenForUsername(response.headers, event.locals.username) } return response } satisfies Handle
Here, needsAuth
is a function that determines whether a request needs authentication.
Further Reading
I didn't take very good notes while sorting this out. But I know I owe thanks to https://github.com/sveltejs/realworld for its many clues.
Thanks also to the authors of SvelteKit's documentation: