Auth

Multi-realm authentication with email/password, Google SSO, GitHub SSO, and JWT tokens.

Realms

An auth realm represents a user population with its own providers, password policy, and user model. An app can have multiple realms — one for admin users, another for customers.

class AdminUserRealm(
    override val deps: AuthSystem.Deps,
    private val usersRepo: AdminUsersRepo,
) : AuthRealm<AdminUser> {

    override val id = "admin-user"

    override val providers = listOf(
        EmailAndPasswordAuth(deps),
        GoogleSsoAuth(deps, clientId = "your-google-client-id"),
        GithubSsoAuth(deps, clientId = "your-github-id", clientSecret = "your-github-secret"),
    )

    override val passwordPolicy = PasswordPolicy.default

    override suspend fun loadUserByEmail(email: String) =
        usersRepo.findByEmail(email)

    override suspend fun createUser(email: String): Stored<AdminUser> {
        return usersRepo.insert(AdminUser(email = email, name = ""))
    }
}

JWT generation

Each realm generates JWT tokens for authenticated users. The token includes user data and permissions, signed with HMAC-512.

override suspend fun generateJwt(
    user: Stored<AdminUser>,
): AuthSignInResponse.Token {
    val gen: JwtGenerator = deps.jwtGenerator

    val token = gen.createJwt(
        user = JwtUserData(
            id = user._key,
            desc = user().name,
            type = "admin",
        ),
        permissions = UserPermissions(isSuperUser = true),
    ) {
        withExpiresAt(Kronos.systemUtc.instantNow().plus(1.hours).jvm)
    }

    return AuthSignInResponse.Token(
        token = token,
        permissionsNs = gen.permissionsNs,
        userNs = gen.userNs,
    )
}

Providers

Each provider handles a specific authentication mechanism:

EmailAndPasswordAuth

Sign up, sign in, password recovery, password change. Passwords are hashed with Argon2/Bcrypt and stored separately from user records.

GoogleSsoAuth

Google OAuth flow. Verifies ID tokens and creates or matches users by email.

GithubSsoAuth

GitHub OAuth flow. Exchanges authorization codes for access tokens.

Wiring in Kontainer

Register your realm and its dependencies as a Kontainer module:

val AdminUserModule = module {
    dynamic(AdminUsersRepo::class)
    dynamic(AdminUserRealm::class)
    dynamic(AdminUserServices::class)
}

// In your main blueprint:
val blueprint = kontainer {
    funktor(
        config = config,
        auth = { useKarango() },
        // ...
    )

    module(AdminUserModule)
}

Auth API

The auth module provides pre-built API endpoints for all auth operations: sign up, sign in, activate account, set password, password recovery, token refresh, and user-specific API access queries. A matching client library (AuthApiClient) is available for Kotlin/JS frontends.

Token refresh

The GET /auth/{realm}/refresh-token endpoint issues a fresh JWT for authenticated users. The server re-loads the user and regenerates the token, so permission changes are picked up automatically.

// Frontend: the token is refreshed automatically by AuthState.
// You can also call it manually:
val response = api.auth.refreshToken().firstOrNull()
// response.data?.token  — the new JWT

API access matrix (ApiAcl)

The GET /auth/my-api-access endpoint returns a flat list of API endpoints the current user can access, with their estimated access level (Granted or Partial). Denied endpoints are filtered out — the response only reveals what the user CAN access.

// Fetch the access matrix once after login
val matrix = api.auth.getMyApiAccess().first().data!!
val acl = ApiAcl(matrix)

// Type-safe lookup using the same endpoint objects from your ApiClient
if (acl.hasAccessTo(MyApiClient.CreateEvent)) {
    button { +"Create Event" }
}

if (acl.hasAnyAccessTo(MyApiClient.GetEvent)) {
    // Partial access — some fields may be redacted
    navLink("/events") { +"Events" }
}

ApiAcl.empty denies everything — safe default before data is loaded. Unknown endpoints also default to Denied (secure-by-default).

Frontend support

Pre-built Kraft components for login pages, password reset, and password change widgets. Auth state management with router middleware that redirects unauthenticated users to the login page.

Session lifecycle

AuthState automatically manages the JWT session lifecycle. Enabled by default with zero configuration:

  • Periodic refresh — checks token expiry every 60 seconds, refreshes 2 minutes before it expires
  • Focus check — verifies token when the browser tab regains focus
  • Graceful expiry — on expiry: saves the current page, logs out, redirects to login. After re-login, navigates back to the previous page
  • Page reload — clears expired tokens from localStorage immediately on app init
// Zero-config: auto-refresh is enabled by default
val auth = authState<MyUser>(
    frontend = AuthFrontend.default(config = AuthFrontendConfig(redirectAfterLogin = Nav.dashboard())),
    api = Apis.auth,
    router = { kraft.router },
)

// With custom config:
val auth = authState<MyUser>(
    frontend = AuthFrontend.default(config = AuthFrontendConfig(redirectAfterLogin = Nav.dashboard())),
    api = Apis.auth,
    router = { kraft.router },
    sessionConfig = AuthSessionConfig(
        checkIntervalMs = 30_000,          // check every 30 seconds
        refreshBeforeExpiryMs = 120_000L,  // refresh 2 minutes before expiry
        onTokenRefreshed = { refreshApiAcl() },
        onSessionExpired = { showLoginDialog() },
    ),
)

// Disable auto-refresh entirely:
sessionConfig = AuthSessionConfig.disabled
The auth module handles the plumbing — token storage, JWT generation, password hashing, recovery tokens, session lifecycle. You implement the user model and the realm. That's it.

Storage backends

Auth records (passwords, recovery tokens) are stored via pluggable backends:

  • useKarango() — ArangoDB via Karango
  • useMonko() — MongoDB via Monko