Skip to content

Example App

Overview

The example-sveltekit project is a complete, working demonstration of @authrim/sveltekit. Built with SvelteKit 2 and Svelte 5, it showcases all major authentication flows — Passkey, Email Code, Social Login — along with server-side session management, protected routes, and the UI component library.

Source code: github.com/authrim/example-sveltekit

Project Structure

example-sveltekit/
├── src/
│ ├── app.d.ts # TypeScript global types
│ ├── app.html # HTML template
│ ├── hooks.server.ts # Server auth handler
│ ├── lib/
│ │ ├── auth.ts # Authrim client singleton
│ │ └── config.ts # Environment config
│ └── routes/
│ ├── +layout.svelte # Root layout (AuthProvider)
│ ├── +layout.server.ts # Server-side auth data
│ ├── +page.svelte # Home page
│ ├── login/
│ │ └── +page.svelte # Login page
│ ├── signup/
│ │ └── +page.svelte # Sign-up page
│ ├── callback/
│ │ └── +page.svelte # OAuth callback handler
│ └── account/
│ ├── +page.svelte # Account settings (protected)
│ └── +page.server.ts # Auth guard
├── .env.example # Environment variable template
├── svelte.config.js # SvelteKit config (Cloudflare adapter)
├── vite.config.ts # Vite config
└── wrangler.toml # Cloudflare Pages deployment

Configuration

Environment Variables

Copy .env.example and fill in your values:

Terminal window
PUBLIC_AUTHRIM_ISSUER=https://your-tenant.authrim.com
PUBLIC_AUTHRIM_CLIENT_ID=your-client-id

Admin Panel Setup

Register the following in your Authrim client settings:

SettingValue
Allowed Redirect URIshttp://localhost:5173/callback
Allowed Originshttp://localhost:5173

Key Files Walkthrough

src/lib/auth.ts — Client Singleton

The auth client is initialized once and reused across the app:

import { createAuthrim, type AuthrimClient } from '@authrim/sveltekit';
import { getAuthConfig } from './config.js';
let authClient: AuthrimClient | null = null;
export async function getAuth(): Promise<AuthrimClient> {
if (authClient) return authClient;
const config = getAuthConfig();
authClient = await createAuthrim({
issuer: config.issuer,
clientId: config.clientId,
});
return authClient;
}
export function clearAuth(): void {
if (authClient) {
authClient.destroy();
authClient = null;
}
}

src/hooks.server.ts — Server Auth Handler

Sets up server-side session management via the SvelteKit handle hook:

import { createAuthHandle } from '@authrim/sveltekit/server';
import { env } from '$env/dynamic/public';
export const handle = createAuthHandle({
issuer: env.PUBLIC_AUTHRIM_ISSUER || '',
clientId: env.PUBLIC_AUTHRIM_CLIENT_ID || '',
callbackPaths: ['/callback'],
});

src/routes/+layout.server.ts — Auth Data for SSR

Makes auth data available to all pages:

import { createAuthLoad } from '@authrim/sveltekit/server';
export const load = createAuthLoad();

src/routes/+layout.svelte — Root Layout

Initializes the auth client on the client side and wraps all pages with AuthProvider:

<script lang="ts">
import { onMount } from 'svelte';
import { AuthProvider } from '@authrim/sveltekit/components';
import type { AuthrimClient } from '@authrim/sveltekit';
let { data, children } = $props();
let auth: AuthrimClient | null = $state(null);
let isInitializing = $state(true);
onMount(async () => {
try {
const { getAuth } = await import('$lib/auth.js');
auth = await getAuth();
} catch (e) {
console.error('[Authrim] Failed to initialize auth:', e);
} finally {
isInitializing = false;
}
});
</script>
{#if isInitializing}
<p>Loading...</p>
{:else if auth}
<AuthProvider
{auth}
initialSession={data.auth?.session ?? null}
initialUser={data.auth?.user ?? null}
>
{@render children()}
</AuthProvider>
{/if}

Key points:

  • The client is imported dynamically inside onMount to avoid SSR issues
  • AuthProvider receives SSR data (initialSession, initialUser) to prevent hydration mismatch
  • All child routes can use getAuthContext() to access the auth client

Page-by-Page Walkthrough

Home Page — src/routes/+page.svelte

The home page reads auth stores to display the current state:

const { session, user, isAuthenticated, loadingState } = auth.stores;
  • Authenticated — Shows user avatar, name, email, session ID, session expiration, and links to account settings
  • Not authenticated — Shows a welcome card with sign-in and sign-up buttons
  • Features listed — Passkey, Email Code, Social Login, Session Management, Passkey Management, Server-side Validation

Login Page — src/routes/login/+page.svelte

Offers three authentication methods:

Passkey (with Conditional UI)

onMount(async () => {
const isAvailable = await auth.passkey.isConditionalUIAvailable();
if (isAvailable) {
auth.passkey.login({ conditional: true }).then((result) => {
if (result.data) goto(redirectTo);
});
}
});

On load, the page starts Conditional UI — the browser’s autofill shows available passkeys. Users can also click the explicit “Sign in with Passkey” button.

Email Code

A two-step flow using EmailCodeForm:

// Step 1: Send code
const result = await auth.emailCode.send(email, { action: 'login' });
if (!result.error) emailStep = 'code';
// Step 2: Verify code
const result = await auth.emailCode.verify(email, code);
if (!result.error) goto(redirectTo);

Social Login

const result = await auth.social.loginWithPopup(provider, { redirectTo });

Supports Google, GitHub, and Apple. Each opens a popup window for authentication.

Sign-Up Page — src/routes/signup/+page.svelte

Multi-method sign-up with Passkey and Email Code:

// Passkey sign-up
const result = await auth.passkey.signUp({ name: name.trim() });
// Email Code sign-up
const result = await auth.emailCode.send(email, {
action: 'signup',
name: name.trim(),
});

Social sign-up reuses the same auth.social.loginWithPopup() flow.

Callback Page — src/routes/callback/+page.svelte

Handles OAuth/social login callbacks:

onMount(async () => {
if (auth.social.hasCallbackParams()) {
const result = await auth.social.handleCallback();
if (result.error) {
error = result.error.message;
} else {
goto(result.data?.redirectTo || '/');
}
}
});

Account Settings — src/routes/account/

A protected route using requireAuth():

+page.server.ts
import { requireAuth } from '@authrim/sveltekit/server';
export const load = requireAuth({
loginUrl: '/login',
redirectParam: 'redirectTo',
});

Unauthenticated users are redirected to /login?redirectTo=/account.

The page displays:

  • User profile (avatar, name, email)
  • Passkey management (register, list, delete) using PasskeyList and PasskeyRegisterButton
  • Session management (list, revoke) using SessionList
  • Sign-out button

User Flow

flowchart LR
    home["/ 
(Home)"] login["/login
(Login)"] signup["/signup
(Sign Up)"] callback["/callback
(Callback)"] account["/account
(Account)"] home -->|"Not signed in"| login home -->|"Not signed in"| signup login -->|"Passkey / Email Code"| home login -->|"Social Login"| callback --> home signup -->|"Passkey / Email Code"| home home -->|"Account Settings"| account account -->|"Sign Out"| home

UI Components Used

The example app uses components from @authrim/sveltekit/ui:

ComponentUsed inPurpose
CardLogin, Signup, HomeContainer layout
ButtonAll pagesActions
InputSignupName input
EmailCodeFormLogin, SignupEmail + OTP two-step form
SocialLoginButtonsLogin, SignupProvider buttons
PasskeyConditionalInputLoginAutofill-based passkey selection
OTPInputSignup6-digit code input
ResendCodeButtonSignupResend with countdown
PasskeyListAccountRegistered passkeys
PasskeyRegisterButtonAccountAdd new passkey
SessionListAccountActive sessions
AuthErrorAll pagesError display
SpinnerHome, CallbackLoading indicator

Running Locally

Terminal window
git clone https://github.com/authrim/example-sveltekit.git
cd example-sveltekit
pnpm install
cp .env.example .env
# Edit .env with your Authrim tenant URL and client ID
pnpm dev

Open http://localhost:5173 in your browser.

Deploying to Cloudflare Pages

The app is pre-configured for Cloudflare Pages:

Terminal window
pnpm build
npx wrangler pages deploy .svelte-kit/cloudflare

Set environment variables in the Cloudflare dashboard:

VariableValue
PUBLIC_AUTHRIM_ISSUERhttps://your-tenant.authrim.com
PUBLIC_AUTHRIM_CLIENT_IDyour-client-id

Update your Authrim client settings to include the production URL in Allowed Redirect URIs and Allowed Origins.

Next Steps