auth

Sign users in and out, protect pages, and check who can do what.

AuthClient Server

Configuration

// config/auth.ts
defineConfig({
  adapter: AuthAdapter<TUser, TCredentials>, // required
  signInPath?: string,                       // where guards send anonymous users
  eager?: boolean,                           // load the session before first render; default true
})

The provider binds auth in the container and, unless eager: false, restores the session during boot.

Session

interface Session<TUser> {
  user: TUser | null
  expiresAt?: number // epoch ms, when known
  can(ability: string, subject?: unknown): boolean
}

The router's requireAuth() and can() guards read context.session, which the provider supplies.

Hooks

Hook Returns
useAuth() The Auth object: session, user, load(), signIn(credentials), signOut(), refresh(), getToken(), subscribe(listener).
useSession() The live Session. Re-renders on sign-in and sign-out.
useUser() session.user.
useCan() session.can.

Adapters

tokenAdapter

tokenAdapter<TUser, TCredentials = { email: string; password: string }>({
  fetchUser: (token) => Promise<TUser | null>,                       // required
  signIn?: (credentials) => Promise<{ token; refreshToken?; user? }>,
  refresh?: (refreshToken) => Promise<{ token; refreshToken? } | null>,
  signOut?: (token) => Promise<void>,
  storage?: TokenStorage,        // default memoryStorage()
  refreshStorage?: TokenStorage, // default memoryStorage()
  abilities?: Abilities<TUser>,
})

memoryStorage() and localStorageTokens(key = 'kitr:token') are the built-in TokenStorages. Write your own with { get(), set(token) }.

betterAuthAdapter

betterAuthAdapter<TUser>(client, { abilities?, signIn?: (client, credentials) => Promise<unknown> })

Uses client.signIn.email unless you pass signIn.

customAdapter

customAdapter<TUser, TCredentials>({
  getSession: () => Promise<Session<TUser> | null>, // required
  signIn?, signOut?, refresh?, getToken?, onChange?,
})

Build sessions with createSession(user, abilities?, { expiresAt? }).

defineAbilities

const abilities = defineAbilities<User>({ 'name': (user, subject?) => boolean })

abilities.can(user, 'name', subject) // false when user is null
abilities.for(user)('name', subject)

kitr/auth/server

No React. Works with any @kitr/api router.

authMiddleware<TUser>({
  verify: (token, ctx) => TUser | null | Promise<TUser | null>, // required
  from?: 'bearer' | { cookie: string }, // default 'bearer'
  optional?: boolean,                   // let anonymous requests through
  abilities?: Abilities<TUser>,
})

requireUser<TUser>(ctx): TUser        // throws 401 when anonymous
currentUser<TUser>(ctx): TUser | null

The middleware sets ctx.locals.user and ctx.locals.can(ability, subject), and throws 401 UNAUTHORIZED when there is no valid user and optional is off.