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 Storage backends
Auth records (passwords, recovery tokens) are stored via pluggable backends: