@wocha/sveltekit
v0.1.0
Published
SvelteKit adapter for Wocha authentication (BFF pattern with httpOnly cookies)
Maintainers
Readme
@wocha/sveltekit
SvelteKit adapter for Wocha authentication using the BFF (Backend-for-Frontend) pattern. OAuth token exchange and refresh happen on the server; sessions are stored in encrypted httpOnly cookies — refresh tokens never reach the browser.
Install
npm install @wocha/sveltekit
# or: pnpm add / yarn add / bun add @wocha/sveltekitPeer dependencies: @sveltejs/kit ^2.0, Svelte 4 or 5.
Quick start
1. Environment variables
WOCHA_CLIENT_ID=your-client-id
WOCHA_CLIENT_SECRET=your-client-secret
WOCHA_ISSUER=https://my-tenant.auth.wocha.ai
# Optional:
WOCHA_API_URL=https://my-tenant.api.wocha.ai2. Auth route handler
Create a catch-all server route at src/routes/auth/[...wocha]/+server.ts:
import { createWochaHandler, wochaAuthConfigFromEnv } from "@wocha/sveltekit/server";
const config = wochaAuthConfigFromEnv() ?? {
clientId: import.meta.env.WOCHA_CLIENT_ID,
clientSecret: import.meta.env.WOCHA_CLIENT_SECRET,
issuer: import.meta.env.WOCHA_ISSUER,
};
export const GET = createWochaHandler(config);
export const POST = createWochaHandler(config);This exposes:
| Route | Method | Purpose |
|-------|--------|---------|
| /auth/login | GET | Start OAuth login (supports ?return_to= and ?signup=1) |
| /auth/callback | GET | OAuth callback — sets session cookie |
| /auth/logout | GET/POST | End session and redirect to IdP logout |
| /auth/session | GET | Return public session JSON for client stores |
| /auth/switch-org | POST | Switch active organisation |
3. Route protection hook
Add to src/hooks.server.ts:
import { greetHandle } from "@wocha/sveltekit/server";
export const handle = greetHandle(undefined, {
publicPaths: ["/", "/about"],
});Unauthenticated requests to protected routes redirect to /auth/login?return_to=….
4. Client stores
Import stores in your Svelte components:
<script lang="ts">
import { session, user, org, signIn, signOut } from "@wocha/sveltekit/client";
</script>
{#if $user}
<p>Signed in as {$user.email}</p>
<p>Organisation: {$org.orgId}</p>
<button on:click={() => signOut()}>Sign out</button>
{:else}
<button on:click={() => signIn()}>Sign in</button>
{/if}5. Conditional components
<script lang="ts">
import { Authenticated, Protect } from "@wocha/sveltekit/client/components";
</script>
<Authenticated>
<Dashboard />
</Authenticated>
<Protect permission={{ resource: { type: "document", id: "doc-1" }, permission: "edit" }}>
<EditForm />
</Protect>Server helpers
Use in +page.server.ts, +layout.server.ts, or form actions:
import { getSession, getUser, requireSession } from "@wocha/sveltekit/server";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
const session = await getSession(event);
// or: const session = await requireSession(event);
return { session };
};| Helper | Description |
|--------|-------------|
| getSession(event) | Returns GreetSession \| null |
| getUser(event) | Returns GreetUser \| null |
| requireSession(event) | Returns session or redirects to login |
| getAccessToken(event) | Returns access token string or null |
URL helpers for links:
import { signInUrl, signOutUrl, signUpUrl } from "@wocha/sveltekit/server";
// <a href={signInUrl('/dashboard')}>Sign in</a>Configuration reference
Pass WochaAuthConfig to createWochaHandler() or greetHandle():
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| clientId | string | Yes | OAuth client ID |
| clientSecret | string | Yes | OAuth client secret (server only) |
| issuer | string | Yes | OIDC issuer URL |
| baseUrl | string | No | App origin for redirect URIs (defaults to request origin) |
| authBasePath | string | No | Auth routes prefix. Default: /auth |
| apiUrl | string | No | Platform API base for org switching |
| postLoginRedirect | string | No | Default post-login path. Default: / |
| postLogoutRedirect | string | No | Default post-logout URL |
| sessionCookieName | string | No | Session cookie name. Default: greet_session |
| sessionSecret | string | No | Cookie encryption secret (defaults to clientSecret) |
| dpop | boolean \| object | No | Enable RFC 9449 DPoP sender-constrained tokens |
Client stores
| Export | Description |
|--------|-------------|
| session | Writable store of GreetSession \| null |
| user | Derived store of the current user |
| org | Derived store with orgId and orgIds |
| greetSession | Full store bundle with refresh(), switchOrg(), and apiUrl |
| createGreetSessionStore() | Custom session/switch-org endpoint paths |
Stores fetch from /auth/session on load and when the window regains focus.
Security model
- Confidential client: Client secret stays on the server. Token exchange never runs in the browser.
- Encrypted httpOnly cookies: Sessions are serialised and encrypted with AES-256-GCM. Key derived from the client secret via PBKDF2.
- PKCE: Login uses PKCE with the verifier stored in a short-lived encrypted cookie.
- ID token verification: Callback verifies
id_tokensignature against issuer JWKS. - No refresh token in the browser:
/auth/sessionreturns onlyuser,accessToken, andexpiresAt.
Structured errors
Server flows throw WochaAuthError with a typed code (e.g. state_mismatch, token_exchange_failed):
import { WochaAuthError } from "@wocha/sveltekit/server";Self-hosted configuration
export const GET = createWochaHandler({
clientId: process.env.WOCHA_CLIENT_ID!,
clientSecret: process.env.WOCHA_CLIENT_SECRET!,
issuer: "https://auth.internal.example.com",
apiUrl: "https://api.internal.example.com",
baseUrl: "https://app.internal.example.com",
});Related packages
| Package | Use when |
|---------|----------|
| @wocha/nextjs | Next.js App Router with the same BFF pattern |
| @wocha/vue | Vue 3 SPA with browser PKCE |
| @wocha/react | React SPA with browser PKCE |
| @wocha/sdk | Management API for users, orgs, and permissions |
Troubleshooting
Redirect URI mismatch
Register {baseUrl}/auth/callback in the Wocha Console. The path must match authBasePath.
Session always null
Confirm WOCHA_CLIENT_SECRET is set and cookies are not blocked. In development, ensure your app origin matches the registered redirect URI.
Permission checks fail on the client
The /auth/session response includes customerApiUrl when configured. Pass apiUrl in config or set WOCHA_API_URL for Wocha Cloud tenants.
