SvelteKit Quickstart 🧡
Add a complete login experience to a SvelteKit app. @faable/auth-js drives the Authorization Code flow with PKCE, stores the session, refreshes tokens, and syncs across tabs. You wire it into Svelte’s reactivity with a single writable store.
This is the same framework-agnostic core as the JavaScript quickstart — createClient + onAuthStateChange + getSession — expressed idiomatically in Svelte. There’s no @faable/auth-helpers-svelte package: Svelte uses the core client directly.
You need one package: @faable/auth-js — no framework helper required.
✅ Prerequisites
In the Faable Dashboard , create a Client for your SPA and configure:
- Allowed Callback URLs:
http://localhost:5173/callback(add your production URL later). - Allowed Logout URLs:
http://localhost:5173. - Allowed Web Origins:
http://localhost:5173.
Note your auth domain (your-domain.auth.faable.link) and Client ID. SPAs are public clients — no client secret is involved.
🛠️ Step 1: Create the App and Install
npx sv create my-app
cd my-app
npm install @faable/auth-jsChoose the SvelteKit minimal template and TypeScript when the scaffolder prompts.
Step 2: Create the Auth Client and Disable SSR
createClient touches window (PKCE runs in the browser), so the client must never run on the server. The simplest setup for a SPA is to turn SSR off for the whole app — add a root +layout.ts:
// src/routes/+layout.ts
export const ssr = false;Now create the client. Keeping it in src/lib makes it importable everywhere via the $lib alias:
// src/lib/auth.ts
import { createClient } from "@faable/auth-js";
export const auth = createClient({
domain: "your-domain.auth.faable.link",
clientId: "YOUR_CLIENT_ID",
redirectUri: window.location.origin + "/callback",
});The client initializes itself on creation: it recovers an existing session from storage, or — on the callback URL — exchanges the PKCE ?code= for tokens.
Step 3: Expose the Session as a Store
Wrap the SDK’s session in a Svelte writable so any component can read it reactively. Subscribe once with onAuthStateChange to keep the store fresh on login, logout, token refresh, and cross-tab changes — then seed it once with getSession().
// src/lib/auth.ts (continued)
import { writable } from "svelte/store";
import type { Session } from "@faable/auth-js";
export const session = writable<Session | null>(null);
// Update the store on every auth change…
auth.onAuthStateChange((_event, current) => session.set(current));
// …and seed it once with the current session.
auth.getSession().then(({ data }) => session.set(data.session));getSession() returns (and auto-refreshes) the current session; onAuthStateChange fires on every subsequent change.
Step 4: Login, User, and Logout
Read the store with Svelte’s $ auto-subscription — no session shows a Sign in button, a session shows the user and a Sign out button.
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { auth, session } from "$lib/auth";
</script>
{#if !$session}
<button on:click={() => auth.signInWithOauthConnection({})}>
Sign in
</button>
{:else}
<p>Hello {$session.user.email}</p>
<button on:click={() => auth.signOut({ returnTo: window.location.origin })}>
Sign out
</button>
{/if}signInWithOauthConnection({})sends the user to your tenant’s Universal Login with every connection you’ve enabled. Target one directly with{ connection_id: "connection_..." }.signOut({ returnTo })clears the local session and the SSO cookie on the auth server.returnTomust be in Allowed Logout URLs.- Both methods redirect the browser on success — the promise intentionally never resolves, so don’t put code after the
await. Post-login logic belongs inonAuthStateChange(Step 3), which already updates the store.
Step 5: Add the Callback Route
Create a /callback route that finishes the login and returns home. handleRedirectCallback() awaits the code-for-tokens exchange (it’s idempotent — the client already started it) and returns { error, returnTo }, so deep links survive the round-trip if you passed returnTo to signInWithOauthConnection({ returnTo }).
<!-- src/routes/callback/+page.svelte -->
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { auth } from "$lib/auth";
let message = "Signing you in…";
onMount(async () => {
const { error, returnTo } = await auth.handleRedirectCallback();
if (error) message = error.message;
else goto(returnTo ?? "/", { replaceState: true });
});
</script>
<p>{message}</p>Step 6: Call Your API
The access token lives on the session. Read it fresh before each call — getSession() auto-refreshes an expired session:
// src/lib/api.ts
import { auth } from "$lib/auth";
export async function apiFetch(path: string) {
const { data, error } = await auth.getSession();
if (error || !data.session) throw new Error("Not signed in");
return fetch(`https://api.myapp.com${path}`, {
headers: { authorization: `Bearer ${data.session.access_token}` },
});
}If your backend validates the token’s audience, pass your API identifier when creating the client (createClient({ ..., audience: "https://api.myapp.com" })) — and see Validate Access Tokens for the Express middleware on the other side.
❓ FAQ
Why do I need export const ssr = false?
createClient and the PKCE flow use window and localStorage, which don’t exist during server-side rendering. Disabling SSR in the root +layout.ts runs the app as a client-side SPA so the auth client is only ever constructed in the browser. If you want to keep SSR for other routes, guard the client-side code with browser from $app/environment instead.
How do I read the session in a component?
Import the session store and use $session — Svelte’s $ prefix auto-subscribes and re-renders on every change (login, logout, refresh, cross-tab sync). The user is at $session.user (e.g. $session.user.email).
How do I get the access token?
From the session: const { data } = await auth.getSession(), then data.session?.access_token. There is no separate getAccessToken() — getSession() already refreshes expired tokens before returning.
How do I send users straight to one provider?
Pass connection_id to signInWithOauthConnection — e.g. { connection_id: "connection_..." }. Find the ID in Dashboard → Auth → Connections. Without it, users pick on the Universal Login screen.
🔗 Related
- JavaScript · Vue · Angular — the same pattern in other frameworks.
- React Quickstart · Next.js Quickstart — with the React helper hooks.
- Authorization Code Flow with PKCE — what the SDK does under the hood.
- Validate Access Tokens — verify these tokens in your backend.
- Connections — enable Google, GitHub, passwordless, and more.
Last updated on