svelte-session
v1.0.5
Published
SvelteKit session client for cookie-backed refresh sessions and in-memory access tokens
Maintainers
Readme
svelte-session
SvelteKit session helpers for applications that use JWT access tokens, with optional refresh endpoint support.
The package keeps the access token in memory, stores session state in Svelte 5 reactive state, and delegates durable session ownership to your backend through a small HTTP contract.
Features
- Svelte 5
SessionProviderfor client hydration and token lifecycle handling. - SvelteKit server helper for SSR session loading.
- In-memory access token store, with no
localStorageorsessionStoragepersistence. - Configurable endpoint paths, cookie name, credentials mode, and token lifecycle timing.
- Backend-agnostic HTTP contract for auth, optional refresh, session introspection, token check, and logout.
Install
npm install svelte-sessionPeer requirements:
npm install svelteAuth Model
The package works with one JWT strategy where refresh is optional. The frontend receives access tokens and keeps them only in memory.
svelte-session deliberately does not validate passwords, sign JWTs, store refresh tokens, or decide cookie policy. Those responsibilities stay on your backend. The package only coordinates SvelteKit SSR, browser state, token expiry handling, and calls to your configured identity endpoints.
Token responsibilities:
- Access token: short-lived bearer token returned to the frontend. It is stored in memory and exposed through
getAccessToken()andgetSession(). - Refresh session (optional): durable backend-owned session, usually represented by an httpOnly cookie. The package never reads the cookie in the browser; it only sends credentialed requests so the backend can read it.
Typical flow:
signIn()calls the configuredauthendpoint.- The backend returns an access token plus user data.
SessionProviderhydrates client state and schedules token refresh ifpaths.refreshis configured, otherwise schedules session termination at token expiry.getServerSession()calls the configuredsessionendpoint during SSR.- If configured, client refresh calls the configured
refreshendpoint. signOut()calls the configuredlogoutendpoint and clears client state.
Token Lifecycle
When SessionProvider mounts, it calls initSession(). If a valid hydrated access token is already present, the package schedules a timer.
If paths.refresh is configured, the timer performs refresh before expiry.
If paths.refresh is not configured, the timer expires the in-memory session when the token lifetime is over.
With refresh enabled, the package schedules the next refresh for:
expiresInSec * 1000 - refreshBeforeExpiryMswith a minimum delay of 5 seconds. With the default config, a 15-minute access token is refreshed about 1 minute before expiry.
There are two important boundaries:
getAccessToken()is synchronous and does not perform network refresh by itself.- Timers can be delayed by browser tab suspension, sleep, or offline periods.
If refresh is disabled, refresh() and refreshTokens() resolve the session to unauthorized when there is no valid in-memory token.
SvelteKit Setup
Create an auth config:
// src/auth.config.ts
import type { AuthConfig } from 'svelte-session';
const authConfig: AuthConfig = {
authUrl: 'http://localhost:3000',
paths: {
auth: '/api/identity/auth',
session: '/api/identity/session',
check: '/api/identity/check',
logout: '/api/identity/logout'
},
cookie: {
sessionName: 'sid',
path: '/'
},
session: {
refreshBeforeExpiryMs: 60_000,
defaultAccessTokenTtlSec: 15 * 60
},
fetchCredentials: 'include'
};
export default authConfig;Load the session on the server:
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { getServerSession } from 'svelte-session/server';
import authConfig from './auth.config';
export const handle: Handle = async ({ event, resolve }) => {
const session = await getServerSession(event, authConfig);
event.locals.user = session.user;
event.locals.accessToken = session.accessToken;
event.locals.accessTokenExpiresInSec = session.accessTokenExpiresInSec;
const response = await resolve(event);
for (const cookie of session.setCookies) {
response.headers.append('set-cookie', cookie);
}
return response;
};Expose SSR data to the root layout:
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
session: {
user: locals.user,
accessToken: locals.accessToken,
accessTokenExpiresInSec: locals.accessTokenExpiresInSec
}
};
};Wrap your app:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { SessionProvider } from 'svelte-session';
import authConfig from '../auth.config';
let { children, data } = $props();
</script>
<SessionProvider config={authConfig} session={data.session}>
{@render children()}
</SessionProvider>Use the client helpers:
<script lang="ts">
import { getSession, signIn, signOut } from 'svelte-session';
const session = getSession();
async function login() {
await signIn({ login: '[email protected]', password: 'password' });
}
</script>
{#if session.status === 'authorized'}
<p>{session.data?.email}</p>
<button type="button" onclick={() => signOut()}>Sign out</button>
{:else}
<button type="button" onclick={login}>Sign in</button>
{/if}API
Module entrypoints
import { SessionProvider, signIn } from 'svelte-session';
import { getServerSession } from 'svelte-session/server';
import { createAuth } from 'svelte-session/core';
import type { SessionUser } from 'svelte-session/types';Internal source paths are not part of the public API and should not be imported directly.
Client exports
import {
SessionProvider,
getAccessToken,
getSession,
refresh,
refreshTokens,
signIn,
signOut
} from 'svelte-session';Useful exported types:
import type {
ApiResponse,
AuthData,
AuthResponseData,
CheckSessionResponseData,
RefreshResponseData,
SessionResponseData
} from 'svelte-session';AuthUser, CheckSessionResponseData, SessionUser, ClientSession, and ServerSession are exported as interfaces so they can be extended through TypeScript module augmentation.
Server exports
import { getServerSession } from 'svelte-session/server';AuthConfig
type AuthConfig = {
authUrl: string;
paths: {
auth: string;
refresh?: string;
session: string;
check: string;
logout: string;
};
cookie?: {
sessionName?: string;
path?: string;
};
session?: {
refreshBeforeExpiryMs?: number;
defaultAccessTokenTtlSec?: number;
};
fetchCredentials?: RequestCredentials;
onAfterSignOut?: () => void | Promise<void>;
};Backend Contract
The package is backend-agnostic. Your backend can be implemented with Hono, SvelteKit endpoints, Express, Fastify, Rails, Laravel, Go, or anything else, as long as it follows the configured HTTP contract.
All endpoints should return JSON in this envelope:
type ApiError = {
code: string;
message: string;
};
type ApiResponse<T> =
| {
success: true;
data: T;
}
| {
success: false;
error: ApiError;
};POST auth
Used by signIn(credentials).
Request:
type AuthData = {
login: string;
password: string;
};Response data:
type AuthResponseData = {
accessToken: string;
expires: number;
user: {
userId: string;
email: string;
};
};If refresh is enabled, the backend should set the refresh-session cookie on success.
POST refresh
Optional endpoint. Used by the browser-side automatic refresh timer and by manual refresh() / refreshTokens() calls when configured.
Response data:
type RefreshResponseData = AuthResponseData;The backend may rotate the refresh-session cookie. If the refresh session has an absolute lifetime, the returned expires value and cookie max age should not exceed the remaining session lifetime.
GET session
Used by getServerSession() during SvelteKit SSR.
Response data:
type SessionResponseData = AuthResponseData;This endpoint is used by SSR when your backend has a cookie session. It should validate the refresh-session cookie and return a short-lived access token without rotating the refresh cookie.
GET check
Used when the client already has a non-expired in-memory access token and needs to resolve the current user without refreshing.
Request header:
Authorization: Bearer <access-token>Response data:
type CheckSessionResponseData = {
userId: string;
email: string;
};POST logout
Used by signOut().
Clears or revokes the refresh session. Response data is null.
Session State
getSession() returns a Svelte reactive state object:
type ClientSession = {
status: 'loading' | 'authorized' | 'unauthorized';
data: { userId: string; email: string } | null;
accessToken: string | null;
};State meanings:
loading: the package is signing in, signing out, hydrating, or refreshing.authorized: user data and an access token are available.unauthorized: no valid session is available.
The access token is also available through getAccessToken(). It returns the current in-memory token or null.
If you pass SSR session data to SessionProvider, include accessTokenExpiresInSec from getServerSession() so the client keeps the real token TTL instead of fallback defaults.
Cookies and CORS
For browser requests, configure the backend to allow credentials and expose any headers required by your deployment. If refresh is enabled, the refresh cookie should be httpOnly.
For same-site deployments, SameSite=Lax is usually sufficient. For cross-site frontend/backend deployments, use SameSite=None; Secure and a compatible CORS policy.
Development
npm install
npm run check
npm run build
npm run pack:dry-runSecurity Notes
- Access tokens are stored in memory only.
- Refresh session cookies should be httpOnly and secure in production.
- Avoid returning different errors for unknown users and invalid passwords.
- Prefer an absolute maximum refresh-session lifetime on the backend.
- SSR session introspection should not rotate refresh tokens.
License
ISC
