Kizaki
Guides

Authentication

Set up users, sessions, and principal fields for apps that need a stable identity model.

Use this guide when your app needs authenticated users and application-specific identity fields such as display name, organization, or plan state.

The auth {} block declares your user-auth model: sign-in methods, session lifetime, password policy, and principal attributes. The auth service exposes /__auth/* endpoints and the auth backend enforces passwords, sessions, OAuth, verification, and recovery. The public HTTP contract stays stable across local dev and production, but local dev uses a simulated OAuth interstitial and dev-only reset/verification shortcuts instead of real provider/email delivery.

Authentication and integrations are separate concerns. providers: [google] lets users sign in with Google, but does not grant your app access to Google Calendar. Use integrations {} for delegated provider access. Machine access via apiKeys {} is also separate from user auth.

The principal block projects identity fields into your policies and server code, so they never rediscover the same joins on every request.

  1. Enable the providers you need in auth.
  2. Set password and linking policy intentionally.
  3. Store app-specific profile data in your own entities.
  4. Project only the fields you need into principal.
auth {
  providers: [email, google, github],
  sessionDuration: 30d,
  linking: manual,

  password {
    minLength: 12,
    blockCommon: true,
    breachCheck: true,
  }

  principal {
    displayName: string @from(Profile.displayName),
    organizationId: Organization.id @selectFrom(OrgMembership.orgId),
  }
}

Current user-auth providers are:

  • email
  • google
  • github

What The Auth Block Drives

The auth {} block controls:

  • available sign-in methods
  • session lifetime
  • account-linking policy
  • password policy for email accounts
  • principal attributes used across policies and server code

It does not control route-level machine auth, third-party delegated integrations, or platform/operator login. Those are separate systems.

Model Identity In Layers

Structure your identity model as three tiers:

  • __User for the built-in authenticated identity.
  • One or more app entities such as Profile or OrganizationMembership for business-specific data.
  • A principal projection that exposes the few fields the rest of your app actually needs.

For a multi-tenant app, the principal typically holds a displayName, a current organizationId, and perhaps a role or plan attribute. Keep it small. A compact principal keeps authorization logic readable.

For a new app:

  1. Start with email.
  2. Add Google or GitHub when the product needs them.
  3. Keep the first version of the principal small.
  4. Set a clear password policy for email accounts.
  5. Leave linking enabled only when you actually want users to attach multiple sign-in methods.

You can always add more providers later.

Recommended baseline for password-based apps:

  • minLength: 12
  • blockCommon: true
  • breachCheck: true

Render Auth UI From Server Config

The compiled auth {} block is authoritative at runtime. If a provider is not declared, the corresponding /__auth/* route fails closed, password/reset flows are unavailable when email is absent, and linking endpoints fail closed when linking: disabled.

Use getAuthConfig() to render only the providers and account-management UI the current app actually enables:

import { getAuthConfig, loginWithGoogle } from '@kizaki/sdk/auth';

const auth = await getAuthConfig();

if (auth.providers.includes('google')) {
  loginWithGoogle();
}

Frontend Auth (SDK)

The SDK provides browser helpers that call the platform's /__auth/* endpoints. Sessions are managed via HttpOnly cookies. No tokens are ever exposed to JavaScript.

Email and password

import { login, signup, logout, getSession } from '@kizaki/sdk/auth';

// Sign up
await signup({ email: 'alice@example.com', password: '...', name: 'Alice' });
// Check the verification email before attempting login.
// If verification mail cannot be issued, signup fails instead of creating
// a stranded unverified account.

// Log in
const { user } = await login({ email: 'alice@example.com', password: '...' });

// Check session
const session = await getSession();
if (session.user) { /* authenticated */ }

// Log out
await logout();

Email/password login is verification-gated by default. Until the verification link is used, login returns email_not_verified.

OAuth (Google, GitHub)

OAuth redirects the browser to the provider. The auth service handles PKCE, state, nonce, and verified identity validation behind the scenes. Your schema just declares the providers.

import { loginWithGoogle, loginWithGithub } from '@kizaki/sdk/auth';

// Navigates to Google sign-in, returns to your app after
loginWithGoogle();

// Or GitHub
loginWithGithub('/dashboard'); // optional return-to path

Only show a provider button if getAuthConfig() reports that provider for the current app.

Hosted vs custom-domain OAuth clients

Kizaki supports two production OAuth callback modes:

  • Hosted/default domain traffic uses the shared Kizaki auth callback domain and the platform-managed OAuth client for that provider.
  • Custom-domain traffic can use a direct callback on the app's own domain with app-specific OAuth credentials.

For the shared callback path, the auth domain only relays the provider callback back to the tenant domain. Kizaki completes the login or provider-link exchange only after it can re-check the tenant-scoped flow cookie, and for linking it also re-checks the tenant session on the tenant domain.

For a direct custom-domain callback, set per-app provider credentials in kizaki/apps/<app_id>/envs/production/auth/oidc with client_id, client_secret, and callback_origin for each provider. Example shape:

{
  "providers": {
    "google": {
      "client_id": "google-client-id",
      "client_secret": "google-client-secret",
      "callback_origin": "https://app.example.com"
    }
  }
}

callback_origin must be an HTTPS origin in production. Cleartext http://... origins are rejected outside the local-dev path.

When the request origin matches that callback_origin, Kizaki uses the app-specific client and a direct callback like https://app.example.com/__auth/callback. Otherwise it falls back to the shared Kizaki callback/client path.

React hooks

import { useAuth, AuthGate } from '@kizaki/sdk/auth/react';

function App() {
  return (
    <AuthGate fallback={<LoginPage />}>
      <Dashboard />
    </AuthGate>
  );
}

function Dashboard() {
  const { user, logout } = useAuth();
  return <p>Welcome, {user?.name}. <button onClick={logout}>Log out</button></p>;
}

function LoginPage() {
  const { login, loginWithGoogle, loading, error } = useAuth();

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const fd = new FormData(e.currentTarget);
    await login({
      email: fd.get('email') as string,
      password: fd.get('password') as string,
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      <button type="submit" disabled={loading}>Log in</button>
      <button type="button" onClick={() => loginWithGoogle()}>Continue with Google</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Listening to auth changes

import { onAuthStateChange } from '@kizaki/sdk/auth';

const unsubscribe = onAuthStateChange((session) => {
  console.log(session.user ? 'logged in' : 'logged out');
});

Machine Access Is Separate

User auth and machine access solve different trust problems.

  • Use auth {} for signed-in users.
  • Use routes(..., auth: session) for user-protected routes.
  • Use apiKeys {} plus routes(..., auth: apiKey(scope)) for machine-to-machine callers.

How Auth Simplifies Application Code

Once auth is configured, the rest of your stack gets simpler:

  • Server functions use getPrincipal().
  • Policies compare against principal.*.
  • Your browser app uses useAuth() or the auth helpers instead of manually threading identity.
  • The identity provider is invisible to users. They see your login page, not a third-party screen.

On this page