Svemix provides you with ⚒️ encrypted "stateless" cookie sessions, how this works is create a session to be stored in the browser cookies via a encrypted seal. The seal stored on the client contains the session data, not your server, making it a "stateless" session from the server point of view. This is a different take than express-session
where the cookie contains a session ID to then be used to map data on the server-side.
Svemix has adopted svelte-kit-cookie-session (which is also developed by me) with some tweaks and modifications / optimizations.
The secret is a private key or list of private keys you must pass at runtime, it should be at least
32 characters
long. Use Password Generator to generate strong secrets.
ts
import type {GetSession } from '@sveltejs/kit';import {handleSession } from 'svemix/session';export constgetSession :GetSession = ({locals }) => {returnProperty 'session' does not exist on type 'Locals'.2339Property 'session' does not exist on type 'Locals'.locals .. session data ;};export consthandle =handleSession ({// This should come from an secret environment variable and never be exposed on github.secret :process .env ['COOKIE_SECRET'] as string,// Pass the getSession function, default uses all data inside locals.session.datagetSession },// Optional own handle function can be passed hereasync function ({event ,resolve }) {// event.locals is populated with the session `event.locals.session` and `event.locals.cookies`;constresponse = awaitresolve (event );returnresponse ;});
It allows you to change the secret used to sign and encrypt sessions while still being able to decrypt sessions that were created with a previous secret.
This is useful if you want to:
Then you can use multiple secrets:
Week 1:
ts
export constCannot find name 'handleSession'.2304Cannot find name 'handleSession'.handle =({ handleSession secret : 'SOME_COMPLEX_SECRET_AT_LEAST_32_CHARS'});
Week 2:
ts
export constCannot find name 'handleSession'.2304Cannot find name 'handleSession'.handle =({ handleSession secret : [{id : 2,secret : 'SOME_OTHER_COMPLEX_SECRET_AT_LEAST_32_CHARS'},{id : 1,secret : 'SOME_COMPLEX_SECRET_AT_LEAST_32_CHARS'}]});
Notes:
id
is required so that we do not have to try every secret in the list when decrypting (the id
is part of the cookies value).string
) was given {id:1}
automatically.If you're updating or destroying the session inside one of your actions
, the Form
Component automatically updates the client store as well. This is really helpful because typically the session would only be set on a full page reload / server side rendering.
If the session already exists, the data get's updated but the expiration time stays the same
There are two ways to update the session via: session.update
and via session.set
;
<script context="module" lang="ts" ssr>
import { redirect } from 'svemix/server';
import { authenticateUser } from '$lib/auth';
import type { Action } from 'svemix';
import type { User } from '@prisma/client';
interface ActionData {
email?: string;
password?: string;
}
export const action: Action<ActionData> = async function ({ request, locals }) {
// @ts-ignore
const body = await request.formData();
const email = body.get('email');
const password = body.get('password');
try {
const { user, errors } = await authenticateUser(email, password);
if (errors.email || errors.password) {
return {
values: {
email,
password
},
errors
};
}
await locals.session.set({ isLoggedIn: true, user })
return redirect('/', 302);
} catch (error) {
return {
values: {
email,
password
},
formError: error.message
};
}
};
</script>
After initializing the session, your locals will be filled with a session JS Proxy, this Proxy automatically sets the cookie if you use locals.session.set
or locals.session.update
and receive the current data via locals.session.data only.
<script context="module" lang="ts" ssr>
import type { Loader } from 'svemix';
import { redirect } from 'svemix/server';
export const loader: Loader<any> = function ({ locals }) {
// Redirect the user to an different page
// The loader runs everytime the session stores updates with one of your actions
// This results in no need to full page reloads.
if (locals.session.data?.isLoggedIn) {
return redirect('/', 302);
}
return {};
};
</script>
<script context="module" lang="ts" ssr>
import type { Action } from 'svemix';
export const action: Action<any, any, Locals> = async function ({ locals, request }) {
// @ts-ignore
const body = await request.formData();
const _action = body.get('_action');
if (_action === 'logout') {
await locals.session.destroy();
}
return {};
};
</script>
<script lang="ts">
import { session } from '$app/stores';
import { Form } from 'svemix';
</script>
{#if $session.isLoggedIn}
<Form>
<input type="hidden" name="_action" value="logout" />
<button type="submit">Logout</button>
</Form>
{:else}
<a href="/auth/login"> Sign in </a>
{/if}
To define global types for your session you can edit your app.d.ts and add something like this:
ts
// app.d.tsinterfaceSessionData {views : number;}// See https://kit.svelte.dev/docs#typescript// for information about these interfacesdeclare namespaceApp {interfaceLocals {session : import('svemix/session').Session <SessionData >;cookies :Record <string, string>;}interfacePlatform {}interfaceSession extendsSessionData {}interfaceStuff {}}