Skip to content

Auth API Reference

@korajs/auth provides authentication, authorization, encryption, and identity management for Kora.js applications.

The package exposes three entry points:

  • @korajs/auth -- Client-side: auth client, device identity, token storage, passkeys, encryption
  • @korajs/auth/server -- Server-side: auth routes, token management, sessions, MFA, orgs, RBAC, OAuth
  • @korajs/auth/react -- React bindings: provider, hooks for auth and org state

Client API

typescript
import {
  createKoraAuth,
  createKoraAuthSync,
  AuthClient,
  AuthError,
  OrgClient,
  OrgClientError,
  TokenStore,
  EncryptedTokenStore,
  generateDeviceKeyPair,
  exportPublicKeyJwk,
  signChallenge,
  verifyChallenge,
  computePublicKeyThumbprint,
  toBase64Url,
  fromBase64Url,
  createDeviceKeyStore,
  IndexedDBDeviceKeyStore,
  InMemoryDeviceKeyStore,
  createAuthTokenStorage,
  createPersistentDeviceIdentity,
} from '@korajs/auth'

AuthClient

Client-side authentication manager. Handles token storage, session restoration, sign-up, sign-in, sign-out, automatic token refresh, and auth state change notifications. Framework-agnostic. Works in browser, Tauri desktop WebView, and mobile JavaScript environments with pluggable storage and fetch support.

Most apps should start with createKoraAuth():

typescript
const auth = createKoraAuth({ serverUrl: 'http://localhost:3001' })
typescript
const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })

createKoraAuth(options)

Creates an AuthClient with production-shaped defaults for offline-first apps.

typescript
const auth = createKoraAuth({
  serverUrl: 'https://acme.example.com',
})

For desktop and mobile apps, pass one credential store and Kora adapts it for token storage and stable device identity:

typescript
const auth = createKoraAuth({
  serverUrl: 'https://acme.example.com',
  credentialStore: secureStore,
  deviceKeyStore,
})
FieldTypeRequiredDefault
serverUrlstringYes--
credentialStoreAuthKeyValueStorageNoBrowser localStorage when available
deviceKeyStoreDeviceKeyStoreNoIndexedDB when available
deviceIdentityAuthDeviceIdentityProvider | falseNoAuto-created when persistent storage exists
storageAuthTokenStorageNoDerived from credentialStore
fetchtypeof fetchNoglobalThis.fetch
storageKeystringNo'kora_auth'

AuthClientConfig

FieldTypeRequiredDefault
serverUrlstringYes--
storageKeystringNo'kora_auth'
storageAuthTokenStorageNoBrowser localStorage, then memory fallback
fetchtypeof fetchNoglobalThis.fetch
deviceIdentityAuthDeviceIdentityProviderNo--

Properties

  • state: AuthState -- Current auth state: 'loading', 'authenticated', or 'unauthenticated'.
  • currentUser: AuthUser | null -- Currently authenticated user, or null.
  • isAuthenticated: boolean -- Whether the user is currently authenticated.

Methods

  • initialize(): Promise<void> -- Restore session from stored tokens. Safe to call multiple times.
  • signUp(params: { email: string; password: string; name?: string; deviceId?: string; devicePublicKey?: string }): Promise<AuthUser> -- Register a new account.
  • signIn(params: { email: string; password: string; deviceId?: string; devicePublicKey?: string }): Promise<AuthUser> -- Sign in with email/password.
  • signInWithOAuth(provider: string, options?): Promise<{ url: string; state: string }> -- Create an OAuth authorization URL and redirect the current browser window by default.
  • getOAuthAuthorizationUrl(provider: string, options?): Promise<{ url: string; state: string }> -- Create an OAuth authorization URL without redirecting. Use this for desktop/mobile handoff flows.
  • completeOAuthSignIn(provider: string, params: { code: string; state: string; deviceId?: string; devicePublicKey?: string }): Promise<AuthUser> -- Complete an OAuth callback and store Kora tokens.
  • linkOAuth(provider: string, params: { code: string; state: string }): Promise<LinkedOAuthAccount> -- Link an OAuth provider to the signed-in user.
  • listLinkedAccounts(): Promise<LinkedOAuthAccount[]> -- List OAuth accounts linked to the signed-in user.
  • unlinkOAuth(provider: string): Promise<void> -- Unlink an OAuth provider from the signed-in user.
  • signOut(): Promise<void> -- Sign out. Clears local tokens and attempts server-side revocation (best-effort).
  • getAccessToken(): Promise<string | null> -- Get a valid access token, auto-refreshing if expired.
  • getSyncToken(): Promise<string | null> -- Alias for getAccessToken(). Used by the sync engine handshake.
  • onAuthChange(callback: (state: AuthState) => void): () => void -- Subscribe to auth state changes. Returns an unsubscribe function.

Example

typescript
const auth = new AuthClient({ serverUrl: 'http://localhost:3001' })
await auth.initialize()

if (!auth.isAuthenticated) {
  await auth.signIn({ email: 'alice@example.com', password: 'secret' })
}

const unsub = auth.onAuthChange((state) => {
  console.log('Auth state:', state)
})

OAuth sign-in for web apps:

typescript
await auth.signInWithOAuth('google')

OAuth sign-in for desktop and mobile apps:

typescript
const { url } = await auth.getOAuthAuthorizationUrl('google')
await openSystemBrowser(url)

// After your loopback server or custom URL scheme receives the provider callback:
await auth.completeOAuthSignIn('google', {
  code,
  state,
})

For Tauri desktop apps, point serverUrl at the remote auth server and pass createKoraAuthSync({ authClient, schema }) to createApp({ sync: { authClient } }). Email/password auth, refresh tokens, MFA, orgs, and RBAC are shared across web and desktop clients. Passkey support depends on the platform WebView's WebAuthn support; use isPasskeySupported() before rendering passkey UI.

For desktop and mobile production apps, prefer createKoraAuth() with a secure credential store:

typescript
import { createKoraAuth } from '@korajs/auth'

const auth = createKoraAuth({
  serverUrl: 'https://acme.example.com',
  credentialStore: secureStore,
  deviceKeyStore,
})

The adapter can wrap Tauri secure storage, Expo SecureStore, iOS Keychain, Android Keystore, or any other sync or async credential store. createKoraAuth() also binds sessions to a stable offline device automatically when persistent key storage exists.

createKoraAuthSync(options)

Creates an AuthSyncBinding for createApp({ sync: { authClient } }). Wires @korajs/auth to Kora sync with minimal boilerplate.

typescript
import { createKoraAuth, createKoraAuthSync } from '@korajs/auth'
import { createApp } from 'korajs'

const authClient = createKoraAuth({ serverUrl: 'http://localhost:3001' })

const app = createApp({
  schema,
  sync: {
    url: 'wss://localhost:3001/kora-sync',
    authClient: createKoraAuthSync({ authClient, schema }),
  },
})
FieldTypeRequiredDescription
authClientAuthSyncClientYesAuth client with getAccessToken() (and optional onAuthChange)
schemaSchemaDefinitionNoWhen set, builds scope map from JWT claims via extractScopeValuesFromClaims()
scopeFromClaims(claims) => Record<string, unknown>NoCustom claim → flat scope value mapping

Returns AuthSyncBinding (alias: KoraAuthSyncBinding, deprecated):

MethodDescription
auth()Returns { token } for sync handshake; empty string when signed out
resolveScopeMap?()Builds per-collection scope map from current token + schema
resolveNodeId?()Returns JWT dev claim as device-bound sync node id
subscribe?(listener)Notifies on auth state change so createApp can reconnect

When schema is provided, scope values are extracted from:

  1. Top-level JWT claims matching schema scope field names
  2. Nested claims.scope object
  3. JWT subuserId when userId is a declared scope field

The store's sync node id is set from the token dev claim, keeping device identity separate from the user id (sub).

Lower-Level Storage Adapters

  • createAuthTokenStorage({ store, prefix? }) -- adapts a sync or async key-value credential store to AuthTokenStorage.
  • createMemoryAuthTokenStorage() -- in-memory token storage for tests, demos, and SSR.
  • createWebStorageAuthTokenStorage(storage, prefix?) -- adapts localStorage or sessionStorage.
typescript
interface AuthKeyValueStorage {
  getItem(key: string): string | null | Promise<string | null>
  setItem(key: string, value: string): void | Promise<void>
  removeItem(key: string): void | Promise<void>
}

Device Identity Provider

  • createPersistentDeviceIdentity({ storage, keyStore?, deviceIdKey?, generateDeviceId? }) -- stores a stable device ID and a non-extractable ECDSA P-256 key pair, then returns the public key during sign-up/sign-in.

By default, the device key pair uses IndexedDB when available. Runtimes without IndexedDB, such as React Native, must pass an explicit keyStore backed by the platform's secure key storage.

AuthUser

typescript
interface AuthUser {
  id: string
  email: string
  name: string | null
}

AuthState

typescript
type AuthState = 'loading' | 'authenticated' | 'unauthenticated'

OrgClient

Client-side organization management. Communicates with the server's org routes.

typescript
const orgClient = new OrgClient({
  serverUrl: 'http://localhost:3001',
  getAccessToken: () => auth.getAccessToken(),
})

OrgClientConfig

FieldTypeRequiredDefault
serverUrlstringYes--
getAccessToken() => Promise<string | null>Yes--

Properties

  • activeOrgId: string | null -- Currently active organization ID.
  • activeOrg: ClientOrganization | null -- Currently active organization.
  • activeRole: string | null -- Current user's role in the active org.

Methods

  • switchOrg(orgId: string): Promise<void> -- Switch to a different organization.
  • clearActiveOrg(): void -- Clear the active organization.
  • createOrg(params: { name: string; slug?: string }): Promise<ClientOrganization> -- Create a new organization.
  • listOrgs(): Promise<ClientOrganization[]> -- List all organizations the user belongs to.
  • getOrg(orgId: string): Promise<ClientOrganization> -- Get a single organization.
  • leaveOrg(orgId: string): Promise<void> -- Leave an organization.
  • listMembers(orgId: string): Promise<ClientMembership[]> -- List members of an org.
  • inviteMember(orgId: string, params: { email: string; role: string }): Promise<ClientInvitation> -- Invite a user.
  • removeMember(orgId: string, userId: string): Promise<void> -- Remove a member.
  • updateMemberRole(orgId: string, userId: string, role: string): Promise<void> -- Update a member's role.
  • onOrgChange(callback: () => void): () => void -- Subscribe to active org changes.

TokenStore

Client-side token persistence backed by localStorage with an in-memory fallback. Used internally by AuthClient.

typescript
import { TokenStore } from '@korajs/auth'

EncryptedTokenStore

AES-256-GCM encrypted localStorage token store. Encrypts tokens at rest with a device-bound key.

typescript
import { EncryptedTokenStore } from '@korajs/auth'

const store = new EncryptedTokenStore({ encryptionKey: key })

EncryptedTokenStoreConfig

FieldTypeRequired
encryptionKeyCryptoKeyYes

Device Identity

Cryptographic device identity using ECDSA P-256 key pairs via the Web Crypto API.

Functions

  • generateDeviceKeyPair(): Promise<CryptoKeyPair> -- Generate an ECDSA P-256 key pair.
  • exportPublicKeyJwk(publicKey: CryptoKey): Promise<JsonWebKey> -- Export public key as JWK.
  • signChallenge(privateKey: CryptoKey, challenge: Uint8Array): Promise<Uint8Array> -- Sign a challenge with the device's private key.
  • verifyChallenge(publicKey: CryptoKey, challenge: Uint8Array, signature: Uint8Array): Promise<boolean> -- Verify a signed challenge.
  • computePublicKeyThumbprint(publicKey: CryptoKey): Promise<string> -- Compute a SHA-256 thumbprint of the public key.
  • toBase64Url(buffer: ArrayBuffer | Uint8Array): string -- Encode bytes to base64url.
  • fromBase64Url(str: string): Uint8Array -- Decode base64url to bytes.

Device Key Store

Persistent storage for device key pairs.

  • createDeviceKeyStore(): DeviceKeyStore -- Create a store (IndexedDB with in-memory fallback).
  • IndexedDBDeviceKeyStore -- IndexedDB-backed store.
  • InMemoryDeviceKeyStore -- In-memory store (testing/development).

DeviceKeyStore Interface

typescript
interface DeviceKeyStore {
  getKeyPair(): Promise<CryptoKeyPair | null>
  saveKeyPair(keyPair: CryptoKeyPair): Promise<void>
  deleteKeyPair(): Promise<void>
}

Passkeys (WebAuthn)

Client-side functions are in @korajs/auth. Server-side functions are in @korajs/auth/server.

Client-Side

typescript
import {
  isPasskeySupported,
  isPlatformAuthenticatorAvailable,
  createPasskeyCredential,
  authenticateWithPasskey,
} from '@korajs/auth'

isPasskeySupported()

Check if WebAuthn is supported in the current environment.

typescript
function isPasskeySupported(): boolean

isPlatformAuthenticatorAvailable()

Check if a platform authenticator (Touch ID, Face ID, Windows Hello) is available.

typescript
async function isPlatformAuthenticatorAvailable(): Promise<boolean>

createPasskeyCredential(options)

Create a passkey credential during registration.

typescript
async function createPasskeyCredential(options: {
  challenge: string           // Base64url-encoded challenge from server
  rpId: string                // Relying party ID (e.g. "example.com")
  rpName: string              // Relying party display name
  userId: string              // Base64url-encoded user ID
  userName: string            // User email or username
  userDisplayName: string     // Human-readable display name
  excludeCredentialIds?: string[]
  authenticatorSelection?: {
    authenticatorAttachment?: 'platform' | 'cross-platform'
    residentKey?: 'required' | 'preferred' | 'discouraged'
    userVerification?: 'required' | 'preferred' | 'discouraged'
  }
}): Promise<PasskeyRegistrationResponse>

Returns:

typescript
interface PasskeyRegistrationResponse {
  credentialId: string      // Base64url-encoded credential ID
  publicKey: string         // Base64url-encoded COSE public key
  clientDataJSON: string    // Base64url-encoded clientDataJSON
  attestationObject: string // Base64url-encoded attestation object
}

authenticateWithPasskey(options)

Authenticate with a passkey during login.

typescript
async function authenticateWithPasskey(options: {
  challenge: string             // Base64url-encoded challenge from server
  rpId: string                  // Relying party ID
  allowCredentialIds?: string[] // Limit to specific credentials
  userVerification?: 'required' | 'preferred' | 'discouraged'
  timeout?: number              // Timeout in ms (default: 60000)
}): Promise<PasskeyAuthenticationResponse>

Returns:

typescript
interface PasskeyAuthenticationResponse {
  credentialId: string       // Base64url-encoded credential ID
  authenticatorData: string  // Base64url-encoded authenticator data
  clientDataJSON: string     // Base64url-encoded clientDataJSON
  signature: string          // Base64url-encoded ECDSA signature
  userHandle: string | null  // Base64url-encoded user handle
}

Example

typescript
if (isPasskeySupported()) {
  // Registration
  const credential = await createPasskeyCredential({
    challenge: serverOptions.challenge,
    rpId: 'example.com',
    rpName: 'My App',
    userId: serverOptions.userId,
    userName: 'alice@example.com',
    userDisplayName: 'Alice',
  })
  // Send credential to server for verification

  // Authentication
  const assertion = await authenticateWithPasskey({
    challenge: serverOptions.challenge,
    rpId: 'example.com',
  })
  // Send assertion to server for verification
}

Server-Side

typescript
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from '@korajs/auth/server'

generateRegistrationOptions(params)

Generate options for creating a new passkey. Returns options to send to the client.

typescript
function generateRegistrationOptions(params: {
  rpId: string
  rpName: string
  userId: string
  userName: string
  userDisplayName: string
  existingCredentialIds?: string[]
}): RegistrationOptions

verifyRegistrationResponse(params)

Verify a registration response from the client. Supports "none" attestation format.

typescript
async function verifyRegistrationResponse(params: {
  credential: PasskeyRegistrationResponse
  expectedChallenge: string
  expectedOrigin: string
  expectedRpId: string
}): Promise<RegistrationVerificationResult>

Returns:

typescript
interface RegistrationVerificationResult {
  verified: boolean
  credentialId: string
  publicKey: string     // Store this for future authentication
  signCount: number
}

generateAuthenticationOptions(params)

Generate options for signing in with a passkey.

typescript
function generateAuthenticationOptions(params: {
  rpId: string
  allowCredentialIds?: string[]
}): AuthenticationOptions

verifyAuthenticationResponse(params)

Verify an authentication response. Checks the ECDSA P-256 signature and validates the sign counter.

typescript
async function verifyAuthenticationResponse(params: {
  assertion: PasskeyAuthenticationResponse
  expectedChallenge: string
  expectedOrigin: string
  expectedRpId: string
  publicKey: string           // Stored COSE public key from registration
  previousSignCount: number   // Stored sign count
}): Promise<AuthenticationVerificationResult>

Returns:

typescript
interface AuthenticationVerificationResult {
  verified: boolean
  newSignCount: number  // Store this to detect cloned authenticators
}

Encryption

typescript
import {
  generateEncryptionKey,
  encryptData,
  decryptData,
  exportKey,
  importKey,
  deriveEncryptionKey,
  generateSalt,
  OperationEncryptor,
  AutoLockManager,
  isEncryptedField,
} from '@korajs/auth'

Database Encryption (AES-256-GCM)

generateEncryptionKey()

Generate a random 256-bit AES-GCM encryption key.

typescript
async function generateEncryptionKey(): Promise<CryptoKey>

encryptData(key, plaintext)

Encrypt data using AES-256-GCM with a randomly generated IV.

typescript
async function encryptData(
  key: CryptoKey,
  plaintext: Uint8Array,
): Promise<{ ciphertext: Uint8Array; iv: Uint8Array }>

decryptData(key, ciphertext, iv)

Decrypt AES-256-GCM encrypted data. Detects tampering via the GCM authentication tag.

typescript
async function decryptData(
  key: CryptoKey,
  ciphertext: Uint8Array,
  iv: Uint8Array,
): Promise<Uint8Array>

exportKey(key)

Export an AES-256-GCM CryptoKey to raw bytes (32 bytes).

typescript
async function exportKey(key: CryptoKey): Promise<Uint8Array>

importKey(rawKey)

Import raw key bytes (must be exactly 32 bytes) into an AES-256-GCM CryptoKey.

typescript
async function importKey(rawKey: Uint8Array): Promise<CryptoKey>

Example

typescript
const key = await generateEncryptionKey()
const data = new TextEncoder().encode('sensitive data')
const { ciphertext, iv } = await encryptData(key, data)

const decrypted = await decryptData(key, ciphertext, iv)
const text = new TextDecoder().decode(decrypted)

Key Derivation (PBKDF2)

deriveEncryptionKey(passphrase, salt?)

Derive an AES-256-GCM key from a passphrase using PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended).

typescript
async function deriveEncryptionKey(
  passphrase: string,
  salt?: Uint8Array,
): Promise<{ key: CryptoKey; salt: Uint8Array }>

If no salt is provided, a random 32-byte salt is generated. The salt must be persisted alongside encrypted data.

generateSalt()

Generate a cryptographically random 32-byte salt.

typescript
function generateSalt(): Uint8Array

Example

typescript
// First time: derive key and store the salt
const { key, salt } = await deriveEncryptionKey('my-passphrase')

// Later: re-derive the same key
const { key: sameKey } = await deriveEncryptionKey('my-passphrase', salt)

OperationEncryptor

End-to-end encryption for Kora sync operations. Encrypts data and previousData fields while leaving sync metadata (id, nodeId, timestamp, causalDeps, etc.) in cleartext.

typescript
const encryptor = new OperationEncryptor({ key })

OperationEncryptorConfig

FieldTypeRequired
keyCryptoKeyYes

Methods

  • encryptOperation(operation: Operation): Promise<Operation> -- Encrypt an operation's data fields. Returns a new operation (immutable).
  • decryptOperation(operation: Operation): Promise<Operation> -- Decrypt an operation's data fields.
  • isEncrypted(operation: Operation): boolean -- Check if an operation's data fields are encrypted.
  • encryptBatch(operations: Operation[]): Promise<Operation[]> -- Encrypt multiple operations in parallel.
  • decryptBatch(operations: Operation[]): Promise<Operation[]> -- Decrypt multiple operations in parallel.

isEncryptedField(field)

Standalone utility to detect encrypted operation fields without an OperationEncryptor instance.

typescript
function isEncryptedField(field: Record<string, unknown> | null): boolean

Example

typescript
const key = await generateEncryptionKey()
const encryptor = new OperationEncryptor({ key })

// Before sending via sync
const encrypted = await encryptor.encryptOperation(operation)
syncEngine.send(encrypted)

// After receiving from sync
const decrypted = await encryptor.decryptOperation(receivedOp)
store.apply(decrypted)

AutoLockManager

Manages inactivity-based auto-locking for the encrypted local store. No DOM dependencies -- uses setTimeout internally and accepts an onLock callback.

typescript
const manager = new AutoLockManager({
  timeout: 15 * 60 * 1000, // 15 minutes
  onLock: () => {
    // Clear decrypted data from memory, show lock screen
  },
})

AutoLockConfig

FieldTypeRequired
timeoutnumberYes
onLock() => voidYes

Properties

  • isLocked: boolean -- Whether the manager is in the locked state.

Methods

  • start(): void -- Begin monitoring for inactivity.
  • stop(): void -- Stop monitoring. Does not change lock state.
  • reportActivity(): void -- Reset the inactivity timer. Call on user interactions.
  • lock(): void -- Manually lock immediately. Invokes onLock.
  • unlock(): void -- Return to unlocked state. Restarts the timer if running.

React API

typescript
import {
  AuthProvider,
  useAuth,
  useCurrentUser,
  useAuthStatus,
  AuthContext,
  OrgContext,
  useOrg,
  useOrgMembers,
  usePermission,
} from '@korajs/auth/react'

<AuthProvider>

React context provider that wraps the AuthClient. Calls client.initialize() on mount and subscribes to auth state changes.

typescript
interface AuthProviderProps {
  client: AuthClient
  children: ReactNode
  fallback?: ReactNode  // Shown while initializing
}

Must be placed above any component that uses useAuth, useCurrentUser, or useAuthStatus.

Example

typescript
import { createKoraAuth } from '@korajs/auth'
import { AuthProvider } from '@korajs/auth/react'

const authClient = createKoraAuth({ serverUrl: 'http://localhost:3001' })

function App() {
  return (
    <AuthProvider client={authClient} fallback={<div>Loading...</div>}>
      <MyApp />
    </AuthProvider>
  )
}

useAuth()

Full authentication hook. Returns user, loading state, error state, and auth methods. Re-renders on state changes. Uses useSyncExternalStore for React 18+ concurrent mode safety.

typescript
function useAuth(): UseAuthResult

Returns:

typescript
interface UseAuthResult {
  user: AuthUser | null
  isAuthenticated: boolean
  isLoading: boolean
  signUp: (params: { email: string; password: string; name?: string }) => Promise<void>
  signIn: (params: { email: string; password: string }) => Promise<void>
  signOut: () => Promise<void>
  error: string | null
}

Example

typescript
function LoginPage() {
  const { user, isAuthenticated, isLoading, signIn, error } = useAuth()

  if (isLoading) return <div>Loading...</div>
  if (isAuthenticated) return <div>Welcome, {user?.name}</div>

  return (
    <form onSubmit={async (e) => {
      e.preventDefault()
      await signIn({ email: 'user@example.com', password: 'secret' })
    }}>
      {error && <p>{error}</p>}
      <button type="submit">Sign In</button>
    </form>
  )
}

useCurrentUser()

Lightweight hook that returns only the current user. Use instead of useAuth when you do not need auth methods or error state.

typescript
function useCurrentUser(): AuthUser | null

Example

typescript
function UserAvatar() {
  const user = useCurrentUser()
  if (!user) return null
  return <span>{user.name ?? user.email}</span>
}

useAuthStatus()

Returns the current auth status. Re-renders only when the auth state changes.

typescript
function useAuthStatus(): AuthStatus

Returns:

typescript
interface AuthStatus {
  state: AuthState
  isAuthenticated: boolean
  isLoading: boolean
}

Example

typescript
function AuthGuard({ children }: { children: React.ReactNode }) {
  const { isAuthenticated, isLoading } = useAuthStatus()
  if (isLoading) return <Spinner />
  if (!isAuthenticated) return <Navigate to="/login" />
  return <>{children}</>
}

useOrg()

Organization management hook. Re-renders when the active organization changes.

typescript
function useOrg(): UseOrgResult

Returns:

typescript
interface UseOrgResult {
  org: ClientOrganization | null
  role: string | null
  orgId: string | null
  switchOrg: (orgId: string) => Promise<void>
  createOrg: (params: { name: string; slug?: string }) => Promise<ClientOrganization>
  leaveOrg: () => Promise<void>
  clearOrg: () => void
  listOrgs: () => Promise<ClientOrganization[]>
  error: string | null
}

Must be used within an OrgContext.Provider.

useOrgMembers(orgId)

Hook for managing organization members. Automatically loads members when orgId changes.

typescript
function useOrgMembers(orgId: string): UseOrgMembersResult

Returns:

typescript
interface UseOrgMembersResult {
  members: ClientMembership[]
  isLoading: boolean
  refresh: () => Promise<void>
  invite: (email: string, role: string) => Promise<ClientInvitation>
  removeMember: (userId: string) => Promise<void>
  updateRole: (userId: string, role: string) => Promise<void>
  error: string | null
}

usePermission(requiredRole)

Check if the current user has at least the specified role level in the active organization.

typescript
function usePermission(requiredRole: string): boolean

Role hierarchy (lowest to highest): viewer < billing < member < admin < owner.

Example

typescript
function AdminPanel() {
  const canManage = usePermission('admin')
  if (!canManage) return <p>Access denied</p>
  return <AdminSettings />
}

Server API

typescript
import {
  createKoraAuthServer,
  createSqliteOAuthStores,
  BuiltInAuthRoutes,
  TokenManager,
  SessionManager,
  TotpManager,
  OrgRoutes,
  RbacEngine,
  OrgScopeResolver,
  // ... and many more
} from '@korajs/auth/server'

createKoraAuthServer(options)

Creates the built-in auth server with sensible defaults for Kora sync.

typescript
const oauthStores = await createSqliteOAuthStores({
  filename: './auth.db',
})

const auth = createKoraAuthServer({
  jwtSecret: process.env.KORA_AUTH_SECRET!,
  oauth: {
    providers: [
      googleProvider({
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        redirectUri: 'https://app.example.com/auth/oauth/google/callback',
      }),
    ],
    stateStore: oauthStores.stateStore,
    linkedIdentityStore: oauthStores.linkedIdentityStore,
  },
})

const server = createProductionServer({
  store,
  syncOptions: {
    auth: auth.auth,
  },
  httpRoutes: [
    {
      path: '/auth',
      handle: auth.handleRequest,
    },
  ],
})
FieldTypeRequiredDefault
jwtSecretstring | string[]ProductionKORA_AUTH_SECRET, dev-only generated secret
userStoreUserStoreNoInMemoryUserStore
tokenManagerTokenManagerNoCreated with revocation store
tokenManagerOptionsOmit<TokenManagerConfig, 'secret'>NoTokenManager defaults
pathstringNo'/auth'
oauthOAuthServerConfigNoOAuth routes disabled
challengeStoreChallengeStoreNoInMemoryChallengeStore
rateLimiterRateLimiterNoInMemoryRateLimiter

The returned object includes:

  • routes -- underlying BuiltInAuthRoutes
  • userStore -- configured user/device store
  • tokenManager -- configured token manager
  • oauth -- configured OAuthManager, when OAuth is enabled
  • linkedIdentityStore -- configured OAuth account-linking store, when OAuth is enabled
  • auth -- sync auth provider for @korajs/server
  • handleRequest() -- one HTTP handler for /auth/*

When oauth is configured, handleRequest() also serves:

RoutePurpose
GET /auth/oauth/:providerCreate an authorization URL and state token. Returns { url, state }.
GET /auth/oauth/:provider/callbackComplete a browser OAuth callback from code and state query params.
POST /auth/oauth/:provider/callbackComplete a desktop/mobile callback from JSON body { code, state, deviceId?, devicePublicKey? }.
GET /auth/oauth/linksList the signed-in user's linked OAuth identities.
POST /auth/oauth/:provider/linkLink a provider to the signed-in user using { code, state }.
DELETE /auth/oauth/:provider/linkUnlink that provider from the signed-in user.

OAuthServerConfig accepts all OAuthManagerConfig fields plus:

FieldTypeDefault
providersOAuthProviderConfig[]Required
linkedIdentityStoreLinkedIdentityStoreInMemoryLinkedIdentityStore
stateStoreOAuthStateStoreInMemoryOAuthStateStore
createNewUsersbooleantrue
autoLinkVerifiedEmailbooleanfalse
allowUnlinkLastIdentitybooleanfalse

By default, Kora does not let a user unlink their last OAuth identity because OAuth-created accounts may not have another usable sign-in method. Enable allowUnlinkLastIdentity only when your app provides another recovery or sign-in path.

BuiltInAuthRoutes

Lower-level server-side route handlers for custom auth wiring. Transport-agnostic -- returns { status, body } response objects to wire into any HTTP framework.

typescript
const routes = new BuiltInAuthRoutes(config)

AuthRoutesConfig

FieldTypeRequiredDefault
userStoreUserStoreYes--
tokenManagerTokenManagerYes--
challengeStoreChallengeStoreNoInMemoryChallengeStore
rateLimiterRateLimiterNoInMemoryRateLimiter

Methods

All handlers return Promise<AuthRouteResponse<T>> where:

typescript
interface AuthRouteResponse<T> {
  status: number
  body: { data: T } | { error: string }
}
  • signUp(params: { email, password, name? }): Promise<AuthRouteResponse<{ accessToken, refreshToken }>> -- Register a new user.
  • signIn(params: { email, password }): Promise<AuthRouteResponse<{ accessToken, refreshToken }>> -- Sign in. Returns token pair.
  • refresh(params: { refreshToken }): Promise<AuthRouteResponse<{ accessToken, refreshToken }>> -- Refresh the access token.
  • signOut(userId: string, params: { refreshToken? }): Promise<AuthRouteResponse<{ success: true }>> -- Revoke tokens.
  • getProfile(userId: string): Promise<AuthRouteResponse<UserProfile>> -- Get the authenticated user's profile.
  • changePassword(userId: string, params: { currentPassword, newPassword }): Promise<AuthRouteResponse<{ success: true }>> -- Change password.
  • registerDevice(userId: string, params: { publicKeyJwk, name? }): Promise<AuthRouteResponse<{ deviceId, challenge }>> -- Register a device key.
  • verifyDevice(userId: string, params: { deviceId, signature }): Promise<AuthRouteResponse<{ verified: true }>> -- Verify device challenge.

Example

typescript
import { BuiltInAuthRoutes, TokenManager, InMemoryUserStore } from '@korajs/auth/server'

const tokenManager = new TokenManager({
  accessTokenSecret: process.env.JWT_SECRET!,
  refreshTokenSecret: process.env.REFRESH_SECRET!,
})

const routes = new BuiltInAuthRoutes({
  userStore: new InMemoryUserStore(),
  tokenManager,
})

// Wire to Express
app.post('/auth/signup', async (req, res) => {
  const result = await routes.signUp(req.body)
  res.status(result.status).json(result.body)
})

app.post('/auth/signin', async (req, res) => {
  const result = await routes.signIn(req.body)
  res.status(result.status).json(result.body)
})

TokenManager

Server-side JWT token creation, verification, and revocation.

typescript
const tokenManager = new TokenManager(config)

TokenManagerConfig

FieldTypeRequiredDefault
accessTokenSecretstringYes--
refreshTokenSecretstringYes--
accessTokenTtlSecondsnumberNo900 (15 min)
refreshTokenTtlSecondsnumberNo604800 (7 days)
issuerstringNo'kora'
revocationStoreTokenRevocationStoreNoInMemoryTokenRevocationStore

Methods

  • createAccessToken(userId: string, claims?: Record<string, unknown>): Promise<string> -- Create a signed access JWT.
  • createRefreshToken(userId: string): Promise<string> -- Create a signed refresh JWT.
  • verifyAccessToken(token: string): Promise<TokenPayload> -- Verify and decode an access token.
  • verifyRefreshToken(token: string): Promise<TokenPayload> -- Verify and decode a refresh token.
  • revokeRefreshToken(token: string): Promise<void> -- Revoke a refresh token.
  • revokeAllUserTokens(userId: string): Promise<void> -- Revoke all tokens for a user.
  • isRevoked(tokenId: string): Promise<boolean> -- Check if a token has been revoked.

SessionManager

Server-side session management with support for sliding window expiry, idle timeout, concurrent session limits, and MFA tracking.

typescript
const sessions = new SessionManager({
  store: new InMemorySessionStore(),
  sessionTtlMs: 7 * 24 * 60 * 60 * 1000,
  idleTimeoutMs: 30 * 60 * 1000,
  maxSessionsPerUser: 5,
})

SessionManagerConfig

FieldTypeRequiredDefault
storeSessionStoreYes--
sessionTtlMsnumberNo7 days
idleTimeoutMsnumberNo30 minutes
maxSessionsPerUsernumberNo10
slidingWindowbooleanNotrue

Methods

  • create(params: CreateSessionParams): Promise<Session> -- Create a new session. Enforces max sessions limit.
  • validate(sessionId: string): Promise<Session> -- Validate a session. Throws if expired or not found.
  • touch(sessionId: string): Promise<Session> -- Update last activity time. Extends expiry if sliding window is enabled.
  • markMfaVerified(sessionId: string): Promise<Session> -- Mark a session as MFA-verified.
  • requireMfa(sessionId: string): Promise<Session> -- Require MFA verification. Throws SessionMfaRequiredError if not verified.
  • revoke(sessionId: string): Promise<void> -- Delete a session.
  • revokeAll(userId: string): Promise<number> -- Revoke all sessions for a user (sign out everywhere).
  • revokeOthers(userId: string, currentSessionId: string): Promise<number> -- Revoke all sessions except the current one.
  • listSessions(userId: string): Promise<Session[]> -- List all active sessions.
  • cleanExpired(): Promise<number> -- Clean up expired sessions.

Session

typescript
interface Session {
  id: string
  userId: string
  deviceId: string | null
  ipAddress: string | null
  userAgent: string | null
  createdAt: number
  lastActiveAt: number
  expiresAt: number
  mfaVerified: boolean
  metadata?: Record<string, unknown>
}

SessionStore Interface

typescript
interface SessionStore {
  create(session: Session): Promise<void>
  getById(sessionId: string): Promise<Session | null>
  update(session: Session): Promise<void>
  delete(sessionId: string): Promise<void>
  listByUserId(userId: string): Promise<Session[]>
  deleteAllForUser(userId: string): Promise<number>
  deleteAllExcept(userId: string, keepSessionId: string): Promise<number>
  cleanExpired(): Promise<number>
}

Built-in: InMemorySessionStore (development/testing).

TotpManager

TOTP-based Multi-Factor Authentication. Implements RFC 6238 (TOTP) and RFC 4226 (HOTP). Compatible with Google Authenticator, Authy, 1Password, and other TOTP apps.

typescript
const totp = new TotpManager({
  issuer: 'MyApp',
  store: new InMemoryTotpStore(),
})

TotpConfig

FieldTypeRequiredDefault
issuerstringYes--
storeTotpStoreYes--
digitsnumberNo6
periodnumberNo30
algorithm'SHA-1' | 'SHA-256' | 'SHA-512'No'SHA-1'
windownumberNo1
recoveryCodesnumberNo8

Methods

  • enable(userId: string, accountName: string): Promise<TotpSetupResult> -- Enable TOTP MFA. Returns a QR code URI and recovery codes.
  • verifySetup(userId: string, code: string): Promise<boolean> -- Confirm MFA setup with a valid code from the authenticator app.
  • verify(userId: string, code: string): Promise<boolean> -- Verify a TOTP code during login.
  • verifyRecoveryCode(userId: string, recoveryCode: string): Promise<boolean> -- Verify a single-use recovery code.
  • regenerateRecoveryCodes(userId: string, totpCode: string): Promise<string[]> -- Regenerate recovery codes. Requires a valid TOTP code.
  • disable(userId: string, code: string): Promise<void> -- Disable MFA. Accepts either a TOTP code or a recovery code.
  • isEnabled(userId: string): Promise<boolean> -- Check if MFA is enabled and verified.
  • remainingRecoveryCodes(userId: string): Promise<number> -- Get the count of remaining recovery codes.

TotpSetupResult

typescript
interface TotpSetupResult {
  secret: string         // Base32-encoded secret (for manual entry)
  uri: string            // otpauth:// URI for QR code generation
  recoveryCodes: string[] // Plaintext recovery codes (shown once)
}

Example

typescript
const totp = new TotpManager({ issuer: 'MyApp', store: new InMemoryTotpStore() })

// Step 1: Enable (show QR code and recovery codes)
const setup = await totp.enable('user-123', 'alice@example.com')

// Step 2: Verify setup
await totp.verifySetup('user-123', '123456')

// Step 3: On login, verify code
const valid = await totp.verify('user-123', '654321')

OrgRoutes

Server-side route handlers for organization management. Enforces authorization (role checks) and input validation.

typescript
const orgRoutes = new OrgRoutes({ orgStore: new InMemoryOrgStore() })

All methods return Promise<OrgRouteResponse<T>>.

Organization Methods

  • createOrg(userId, params: { name, slug?, metadata? }) -- Create an org. The caller becomes the owner. Status: 201.
  • getOrg(userId, orgId) -- Get an org by ID. Requires membership.
  • updateOrg(userId, orgId, params: { name?, slug?, metadata? }) -- Update an org. Requires admin+.
  • deleteOrg(userId, orgId) -- Delete an org. Requires owner.
  • listUserOrgs(userId) -- List all orgs the user belongs to.

Member Methods

  • addMember(userId, orgId, params: { targetUserId, role }) -- Add a member. Requires admin+.
  • removeMember(userId, orgId, targetUserId) -- Remove a member. Requires admin+ (or self-removal).
  • updateMemberRole(userId, orgId, params: { targetUserId, role }) -- Update a member's role. Requires admin+.
  • listMembers(userId, orgId) -- List all members. Requires membership.
  • transferOwnership(userId, orgId, params: { newOwnerId }) -- Transfer ownership. Requires owner.

Invitation Methods

  • createInvitation(userId, orgId, params: { email, role }) -- Create an invitation. Requires admin+.
  • acceptInvitation(userId, params: { token }) -- Accept an invitation by token.
  • revokeInvitation(userId, orgId, invitationId) -- Revoke a pending invitation. Requires admin+.
  • listPendingInvitations(userId, orgId) -- List pending invitations. Requires admin+.
  • listMyInvitations(email) -- List invitations for a user's email.

Org Roles

Roles in order of ascending privilege: viewer, billing, member, admin, owner.

RbacEngine

Permission evaluation engine for role-based access control with role inheritance and wildcard matching.

typescript
const rbac = new RbacEngine(orgStore)
// or with custom roles:
const rbac = new RbacEngine(orgStore, { roles: customRoles })

Methods

  • hasPermission(userId: string, orgId: string, permission: Permission): Promise<boolean> -- Check if a user has a permission.
  • getUserPermissions(userId: string, orgId: string): Promise<Permission[]> -- Get all effective permissions.
  • getRolePermissions(roleName: string): Permission[] -- Get permissions for a role (including inherited).
  • roleHasPermission(roleName: string, permission: Permission): boolean -- Check if a role has a permission.
  • registerScopeResolver(collection: string, resolver: CollectionScopeResolver): void -- Register a custom scope resolver.
  • resolveScopes(userId: string, orgId: string, collections?: string[]): Promise<SyncScopes | null> -- Resolve sync scopes.
  • getRoleNames(): string[] -- Get all defined role names.
  • getRoleDefinition(roleName: string): RoleDefinition | null -- Get a role definition.

defineRoles()

Builder for defining custom roles with inheritance.

typescript
const roles = defineRoles()
  .role('viewer', ['*:read'])
  .role('editor', ['*:write'], { inherits: ['viewer'] })
  .role('admin', ['org:manage-members'], { inherits: ['editor'] })
  .build()

Permission Format

Permissions are strings in the format resource:action (e.g., todos:read, *:write). The * wildcard matches any resource or action.

OrgScopeResolver

Resolves sync scopes for org-aware data filtering. Combines org membership with RBAC permissions.

typescript
const resolver = new OrgScopeResolver(orgStore, rbacEngine)

Methods

  • registerCollectionScope(collection: string, resolver: CollectionScopeResolver): void -- Register a custom scope for a collection.
  • resolve(userId: string, orgId: string, collections: string[]): Promise<SyncScopes | null> -- Resolve scopes for all collections.
  • canWrite(userId: string, orgId: string, collection: string): Promise<boolean> -- Check write access.
  • canRead(userId: string, orgId: string, collection: string): Promise<boolean> -- Check read access.

Example

typescript
const resolver = new OrgScopeResolver(orgStore, rbacEngine)

// Custom: members only see their own todos
resolver.registerCollectionScope('todos', (ctx) => {
  if (ctx.role === 'member') {
    return { orgId: ctx.orgId, userId: ctx.userId }
  }
  return { orgId: ctx.orgId }
})

const scopes = await resolver.resolve('user-1', 'org-1', ['todos', 'projects'])

OAuth / Social Login

typescript
import {
  OAuthManager,
  InMemoryLinkedIdentityStore,
  InMemoryOAuthStateStore,
  SqliteLinkedIdentityStore,
  SqliteOAuthStateStore,
  PostgresLinkedIdentityStore,
  PostgresOAuthStateStore,
  createSqliteOAuthStores,
  createPostgresOAuthStores,
  googleProvider,
  githubProvider,
  microsoftProvider,
} from '@korajs/auth/server'

Built-in provider configs for Google, GitHub, and Microsoft. The OAuthManager handles the OAuth2 authorization code flow: generating authorization URLs, exchanging codes for tokens, and fetching user info. Most apps should configure OAuth through createKoraAuthServer({ oauth }) so Kora also creates users, issues Kora tokens, registers devices, and stores linked identities.

For desktop and mobile OAuth, enable PKCE and omit clientSecret for public native clients:

typescript
const oauth = new OAuthManager({
  providers: [
    googleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      redirectUri: 'com.acme.app:/oauth/callback',
      pkce: true,
    }),
  ],
})

When pkce: true is set, authorization URLs include an S256 code_challenge and token exchange includes the matching code_verifier.

For production, provide durable stores for OAuth state and linked identities. The in-memory stores are for development and tests.

typescript
const oauthStores = await createPostgresOAuthStores({
  connectionString: process.env.DATABASE_URL!,
})

const auth = createKoraAuthServer({
  jwtSecret: process.env.KORA_AUTH_SECRET!,
  oauth: {
    providers: [googleProvider({ clientId, clientSecret, redirectUri })],
    stateStore: oauthStores.stateStore,
    linkedIdentityStore: oauthStores.linkedIdentityStore,
  },
})

Password Reset

typescript
import {
  PasswordResetManager,
  InMemoryPasswordResetStore,
} from '@korajs/auth/server'

Manages password reset token generation, validation, and consumption with rate limiting.

Email Verification

typescript
import {
  EmailVerificationManager,
  InMemoryEmailVerificationStore,
} from '@korajs/auth/server'

Manages email verification tokens for confirming user email addresses.

External Auth Providers

typescript
import {
  ExternalJwtProvider,
  createClerkAdapter,
  createSupabaseAdapter,
} from '@korajs/auth/server'
  • ExternalJwtProvider -- Validate JWTs from external identity providers.
  • createClerkAdapter(config) -- Create a Clerk auth adapter.
  • createSupabaseAdapter(config) -- Create a Supabase auth adapter.

Admin API

typescript
import { AdminApi } from '@korajs/auth/server'

Administrative operations for managing users: list, search, update, ban, and delete users. Returns paginated results.

Audit Logging

typescript
import { AuditLogger, InMemoryAuditLogStore } from '@korajs/auth/server'

Structured audit logging for auth events (sign-in, sign-out, password change, MFA enable/disable, etc.).

Webhooks

typescript
import {
  WebhookManager,
  InMemoryWebhookStore,
  verifyWebhookSignature,
} from '@korajs/auth/server'

Register webhook endpoints and receive notifications for auth events. Signatures are HMAC-SHA256 for verification.

JWT Utilities

typescript
import { encodeJwt, decodeJwt, verifyJwt, isExpired } from '@korajs/auth/server'
  • encodeJwt(payload, secret): Promise<string> -- Create a signed JWT.
  • decodeJwt(token): TokenPayload | null -- Decode a JWT without verification.
  • verifyJwt(token, secret): Promise<TokenPayload> -- Verify and decode a JWT.
  • isExpired(payload): boolean -- Check if a JWT payload is expired.

Password Hashing

typescript
import { hashPassword, verifyPassword } from '@korajs/auth/server'
  • hashPassword(password: string): Promise<string> -- Hash a password using PBKDF2.
  • verifyPassword(password: string, hash: string): Promise<boolean> -- Verify a password against a hash.

Types

Shared Types (from @korajs/auth)

typescript
type AuthState = 'loading' | 'authenticated' | 'unauthenticated'

interface AuthUser {
  id: string
  email: string
  name: string | null
}

interface AuthTokens {
  accessToken: string
  refreshToken: string
}

type TokenType = 'access' | 'refresh'

interface TokenPayload {
  sub: string          // User ID
  type: TokenType
  iat: number          // Issued at (seconds)
  exp: number          // Expiry (seconds)
  jti: string          // Token ID
  iss?: string         // Issuer
  [key: string]: unknown
}

type AuthEventType =
  | 'signUp'
  | 'signIn'
  | 'signOut'
  | 'tokenRefresh'
  | 'sessionExpired'

interface AuthEvent {
  type: AuthEventType
  userId?: string
  timestamp: number
}

Organization Types (from @korajs/auth/server)

typescript
type OrgRole = 'owner' | 'admin' | 'member' | 'viewer' | 'billing'

interface Organization {
  id: string
  name: string
  slug: string
  ownerId: string
  createdAt: number
  updatedAt: number
  metadata?: Record<string, unknown>
}

interface Membership {
  orgId: string
  userId: string
  role: OrgRole
  joinedAt: number
  invitedBy: string | null
}

interface OrgInvitation {
  id: string
  orgId: string
  email: string
  role: OrgRole
  token: string
  status: InvitationStatus
  invitedBy: string
  createdAt: number
  expiresAt: number
}

type InvitationStatus = 'pending' | 'accepted' | 'revoked' | 'expired'

RBAC Types (from @korajs/auth/server)

typescript
type Permission = string  // Format: "resource:action" (e.g., "todos:read", "*:write")

interface RoleDefinition {
  name: string
  permissions: Permission[]
  inherits?: string[]
}

interface ScopeFilter {
  orgId?: string
  userId?: string
  __readonly?: boolean
  [key: string]: unknown
}

type SyncScopes = Record<string, ScopeFilter>

interface ScopeContext {
  userId: string
  orgId: string
  role: string
  permissions: Permission[]
}

type CollectionScopeResolver = (ctx: ScopeContext) => ScopeFilter | null

Error Classes

All errors extend KoraError from @korajs/core.

ErrorCodeEntry Point
AuthErrorvaries@korajs/auth
OrgClientErrorvaries@korajs/auth
DeviceIdentityErrorDEVICE_IDENTITY_ERROR@korajs/auth
CryptoUnavailableErrorCRYPTO_UNAVAILABLE@korajs/auth
DeviceKeyStoreErrorDEVICE_KEY_STORE_ERROR@korajs/auth
EncryptedTokenStoreErrorvaries@korajs/auth
PasskeyErrorPASSKEY_ERROR@korajs/auth
PasskeyUnsupportedErrorPASSKEY_UNSUPPORTED@korajs/auth
EncryptionErrorENCRYPTION_ERROR@korajs/auth
KeyDerivationErrorKEY_DERIVATION_ERROR@korajs/auth
OperationEncryptionErrorOPERATION_ENCRYPTION_ERROR@korajs/auth
PasskeyVerificationErrorPASSKEY_VERIFICATION_ERROR@korajs/auth/server
SessionErrorvaries@korajs/auth/server
SessionNotFoundErrorSESSION_NOT_FOUND@korajs/auth/server
SessionExpiredErrorSESSION_EXPIRED@korajs/auth/server
SessionLimitExceededErrorSESSION_LIMIT_EXCEEDED@korajs/auth/server
SessionMfaRequiredErrorSESSION_MFA_REQUIRED@korajs/auth/server
TotpErrorvaries@korajs/auth/server
TotpInvalidCodeErrorTOTP_INVALID_CODE@korajs/auth/server
TotpNotEnabledErrorTOTP_NOT_ENABLED@korajs/auth/server
TotpAlreadyEnabledErrorTOTP_ALREADY_ENABLED@korajs/auth/server
TotpNotVerifiedErrorTOTP_NOT_VERIFIED@korajs/auth/server
TotpRecoveryExhaustedErrorTOTP_RECOVERY_EXHAUSTED@korajs/auth/server
OrgErrorvaries@korajs/auth/server
OrgNotFoundErrorORG_NOT_FOUND@korajs/auth/server
OrgSlugTakenErrorORG_SLUG_TAKEN@korajs/auth/server
MembershipNotFoundErrorMEMBERSHIP_NOT_FOUND@korajs/auth/server
MemberAlreadyExistsErrorMEMBER_ALREADY_EXISTS@korajs/auth/server
InsufficientRoleErrorINSUFFICIENT_ROLE@korajs/auth/server
CannotRemoveOwnerErrorCANNOT_REMOVE_OWNER@korajs/auth/server
InvitationNotFoundErrorINVITATION_NOT_FOUND@korajs/auth/server
InvitationExpiredErrorINVITATION_EXPIRED@korajs/auth/server
RbacErrorvaries@korajs/auth/server
RoleNotFoundErrorROLE_NOT_FOUND@korajs/auth/server
CircularInheritanceErrorCIRCULAR_INHERITANCE@korajs/auth/server
OAuthErrorvaries@korajs/auth/server
PasswordResetErrorvaries@korajs/auth/server
EmailVerificationErrorvaries@korajs/auth/server
AdminApiErrorvaries@korajs/auth/server
WebhookErrorvaries@korajs/auth/server