Skip to Content

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 quickstartcreateClient + 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-js

Choose 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. returnTo must 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 in onAuthStateChange (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.


Last updated on

Last updated on