Skip to main content
Urbicon UI

Auth

Authentication, user management, and notifications for a SvelteKit app: password and passkey login, sessions, registration with invitation gates, password reset, two-factor, and push. No runtime dependencies; sessions, passwords, passkeys and push all run on the Web Crypto API.

Overview

@urbicon-ui/auth covers registration, login (password or passkey), password reset, sessions, two-factor, and notifications, with zero runtime dependencies. Each flow has two halves: a server handler you mount on an API route, and a UI component that calls it.

All UI components use @urbicon-ui/blocks primitives, support unstyled / slotClasses / snippet overrides, and read the locale from the @urbicon-ui/i18n context. English is built in; every other locale needs its bundle registered once with registerAuthLocale.

For the complete reference — architecture, staged setup, federation (SSO), the adapter contract, and the known-limitations catalog with the production checklist — see the Auth Reference (AUTH.md).

Architecture

Each auth flow has two sides: a UI component (client) and a handler factory (server). The UI component sends a fetch to your SvelteKit API route; the handler factory is the endpoint behind it. What each handler verifies, hashes, or rate-limits is the last column below.

UI ComponentServer Handler
LoginPagecreateLoginHandler
RegisterPagecreateRegisterHandler
ForgotPasswordPagecreateForgotPasswordHandler
ResetPasswordPagecreateResetPasswordHandler
VerifyEmailPagecreateVerifyEmailHandler
PasskeyManagercreatePasskeyHandlers
InvitationManagercreateInvitationHandlers
NotificationListenercreateStreamHandler

All server handlers are imported from @urbicon-ui/auth/server. Database access goes through the Adapter pattern: a Prisma adapter is included, and custom adapters implement the repository interfaces.

Auth Pages

Management

Notifications

Setup Guide

Integration requires five steps: configure dependencies, add the hook, create API routes, add UI pages, and import the stylesheet.

1. Configure auth dependencies

// src/lib/server/auth.ts
import { createAuthDeps } from '@urbicon-ui/auth/server';
import { createPrismaRepos } from '@urbicon-ui/auth/server/adapters/prisma';
import { createLettermintTransport } from '@urbicon-ui/auth/server/email/lettermint';
import { prisma } from '$lib/server/db';
import { appLogger } from '$lib/server/logging';
import { APP_URL, JWT_SECRET, LETTERMINT_TOKEN } from '$env/static/private';

export const authDeps = createAuthDeps({
  config: {
    // Required, and never derived from request.url — the Host header is
    // attacker-controlled and would point reset links at their domain.
    appUrl: APP_URL,
    jwt: { secret: JWT_SECRET },
    password: { minLength: 8 },
    lockout: { maxAttempts: 5, durationMinutes: 15 },
    routes: { loginPage: '/auth/login' },
    logger: appLogger
  },
  // Same sink: a missing Prisma model drops its feature, reported here.
  repos: createPrismaRepos(prisma, { logger: appLogger }),
  email: createLettermintTransport({ token: LETTERMINT_TOKEN, from: 'noreply@example.com' })
});

2. Add the SvelteKit hook

The handle hook validates the session, redirects unauthenticated requests off protected routes, and adds CSRF and security headers. It takes one options object; the redirect target is config.routes.loginPage from step 1, not a hook option.

// src/hooks.server.ts
import { createAuthHandle, DEFAULT_PUBLIC_ROUTES } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth';

export const handle = createAuthHandle({
  config: authDeps.config,
  repos: authDeps.repos,
  // publicRoutes REPLACES the defaults, so spread them in — without that the
  // /api/auth/* endpoints below lose their exemption and login answers 401.
  // A string is a startsWith prefix ('/' alone would exempt the whole app);
  // the exact form publishes one pathname — here the landing page.
  publicRoutes: [...DEFAULT_PUBLIC_ROUTES, { path: '/', exact: true }, '/pricing']
});

3. Create API route handlers

Each UI component expects a corresponding API endpoint. The handler factories include all validation, hashing, rate limiting, and security.

// Each auth flow needs a SvelteKit API route:
// src/routes/api/auth/login/+server.ts
import { createLoginHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth';
export const POST = createLoginHandler(authDeps);

// src/routes/api/auth/register/+server.ts
import { createRegisterHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth';
export const POST = createRegisterHandler(authDeps);

// src/routes/api/auth/forgot-password/+server.ts
import { createForgotPasswordHandler } from '@urbicon-ui/auth/server';
import { authDeps } from '$lib/server/auth';
export const POST = createForgotPasswordHandler(authDeps);

// Same pattern for: reset-password, verify-email, logout, me

4. Add UI pages

Components read the locale from @urbicon-ui/i18n, so no t prop is needed when the i18n context is set up. Only English is built in — for any other locale call registerAuthLocale('de', de) once at app start, with the bundle from @urbicon-ui/auth/i18n/de.

<!-- src/routes/auth/login/+page.svelte -->
<script>
  import { LoginPage } from '@urbicon-ui/auth';
  import { goto } from '$app/navigation';
</script>

<!-- Locale comes from the i18n context -->
<LoginPage
  onSuccess={() => goto('/')}
  passkeyApiPath="/api/auth/passkey"
  rememberMe
/>

5. Import the stylesheet

A Tailwind build never scans node_modules on its own. The package ships a stylesheet whose @source directive points Tailwind at its components — import it after the blocks one, or the sm: layouts and the link colour of the auth pages are missing from the compiled CSS.

/* app.css */
@import 'tailwindcss';
@import '@urbicon-ui/blocks/style/index.css'; /* tokens + the blocks @source */
@import '@urbicon-ui/auth/style/index.css'; /* the auth @source */