BitaKit
API reference

Client authentication

The shared session contract, login destinations and client rendering boundaries.

Core owns the AuthService contract. Optional provider plugins supply the session; @bitakit/ui observes it and uses the existing framework navigation binding. There is no second session store. Server authentication and authorization remain with the provider and application server.

Better Auth and application paths

This assumes the Better Auth adapter and the generated BitaKit provider are already configured. Keep the provider's existing client and server setup. The authClient import below refers to the application's existing client module; adjust that path to your project.

// src/configs/better-auth.ts
import { defineConfig } from '@bitakit/core';
import type { BetterAuthOptions } from '@bitakit/better-auth';

export default defineConfig({
  defaults: {
    paths: { signIn: '/account/login', afterSignIn: '/customers', afterSignOut: '/' },
  } satisfies BetterAuthOptions,
});
// src/services/better-auth-client.ts
import { defineService } from '@bitakit/core';
import { betterAuthClient } from '@bitakit/better-auth';
import { authClient } from '@/lib/auth-client';

export default defineService(() => ({ client: authClient }), { provides: betterAuthClient });

Paths are application-owned, safe root-relative URLs. No routes are created. useAuth().signIn({ returnTo }) navigates to the configured login page with a returnTo query parameter, unless the adapter supplies a native signIn handler. External or login-loop return destinations fall back to afterSignIn.

The login page must pass resolveAuthReturnTo(paths, returnToFromURL) to the native provider login API as its completion destination. afterSignIn is a fallback, not an automatic session-change redirect. Providers requiring absolute callbacks should resolve the validated path against the configured application origin, not the authentication-server origin. Keep secrets and server configuration on the server.

'use client';
import type { ReactNode } from 'react';
import { AuthBoundary } from '@bitakit/ui';
import { LoadingPage, ErrorPage } from '@bitakit/shell/pages';

export function ProtectedContent({ children }: { children: ReactNode }) {
  return (
    <AuthBoundary pending={<LoadingPage />} renderError={() => <ErrorPage />}>
      {children}
    </AuthBoundary>
  );
}

useAuth from @bitakit/ui exposes the current snapshot and signIn/signOut. Bind these methods through your existing controls and handle rejected promises through the host's error presentation.

AuthBoundary waits for pending session resolution and does not redirect on session errors. redirect={false} allows host-owned unauthenticated presentation. It is a client rendering convenience, never a security boundary. Keep native server checks. useAuth().signOut() awaits the adapter and then navigates to afterSignOut; the boundary does not race that operation with a login redirect. A subsequent protected route can start login normally. Calling a native SDK or service directly does not invoke this UI navigation policy. Configure only one owner for logout navigation.

Writing another adapter

Implement the existing AuthService and register it as a service that provides the authService contract, in an ordinary plugin. No Core change, provider registry or host bootstrap is needed. The following is an adapter template: source is a normalized SDK bridge that the adapter author implements using their provider's public APIs, not an Auth.js or Clerk API.

import {
  authService,
  configureAuthPaths,
  definePlugin,
  defineService,
  type AuthPaths,
  type AuthService,
} from '@bitakit/core';

export function customAuthPlugin(source: AuthService, paths: AuthPaths) {
  const signIn = source.signIn?.bind(source);
  return definePlugin({
    id: 'custom-auth',
    services: {
      // Reached as services['custom-auth'].session and, through the contract, services.auth.
      session: defineService(
        (): AuthService => ({
          paths: configureAuthPaths(paths),
          getSnapshot: () => source.getSnapshot(),
          subscribe: (listener) => source.subscribe(listener),
          signOut: () => source.signOut(),
          ...(signIn ? { signIn } : {}),
        }),
        { provides: authService },
      ),
    },
  });
}

Inside a plugin folder the same service is services/session.ts with the options as the second argument; discovery derives the ID.

The bridge must return a stable { user, pending, error, organizationId? } snapshot until it changes; user contains at least id, and null means signed out only once pending is false and error is absent. Subscribe to native SDK updates and return an unsubscribe function. Preserve SDK failures, await sign-out, and release any adapter-owned resources through the ordinary service/plugin lifecycle. Keep React-only SDK providers inside the optional integration package using existing plugin providers.

Check an adapter in its own tests with assertAuthContract from @bitakit/core/testing. It drives the provider's login through signIn and rejects with the first broken rule: an unstable snapshot, a missing unsubscribe, a non-string user.id, a signOut that resolves before the user is cleared, or invalid paths.

import { assertAuthContract } from '@bitakit/core/testing';

await assertAuthContract(runtime.services.auth, {
  signIn: () => runtime.services.auth.signInWithPassword({ identifier: 'ada', password: 'test' }),
});

Auth.js and Clerk adapters are not implemented yet. Their native session/login APIs must be mapped inside their optional packages; organizations, registration and password recovery are not standardized by this small contract and stay on the provider's own service, which services.auth exposes with the provider's full type. @bitakit/better-auth and @bitakit/strapi (Users & Permissions, with registration, password reset and provider login) are working references.

organizationId identifies the current settings scope when supplied by the provider; it is not proof of organization membership. Server access checks remain with the provider. See Integrations for available adapters and manual composition for providing a contract without discovery.

On this page