Skip to content

Authentication

Every call mero-kotlin makes carries a JWT access token. There are two ways to obtain one — direct credentials and hosted SSO — and the SDK keeps it fresh for you.

For first-party apps, exchange a username and password for a token bundle. bootstrapSecret is only needed for the very first login on a fresh node.

mero.authenticate(
Credentials(
username = "alice",
password = "s3cr3t",
bootstrapSecret = "…", // only on a fresh node's first login
)
)
val authed = mero.isAuthenticated // true

You can also put credentials in the config and call authenticate() with no argument:

val mero = Mero(
MeroConfig(
baseUrl = "https://your-node.example",
credentials = Credentials(username = "alice", password = "s3cr3t"),
)
)
mero.authenticate() // uses config.credentials, else throws MeroStateException

Refresh is reactive: the SDK never refreshes proactively (the server rejects a refresh while the access token is still valid). A 401 with x-auth-error: token_expired drives a single refresh and one retry — invisible to the caller.

A single-flight RefreshCoordinator inside Mero serialises refresh so concurrent requests that all hit a 401 share one in-flight refresh. Because refresh tokens are single-use (rotated on every refresh), it re-reads the token store first — so if another process or a background worker already rotated the token, that newer bundle is adopted instead of replaying a consumed one (which would revoke the whole token family). Supplying a refreshLockFile in the config extends that guarantee across processes via an exclusive file lock.

For hosted login, open the node’s login URL, and feed the callback deep link back in — the native analog of the web redirect flow. On Android, SsoLauncher opens the URL in a Chrome Custom Tab:

import com.calimero.mero.Mero
import com.calimero.mero.auth.AuthLoginOptions
import com.calimero.mero.auth.SsoLauncher
import com.calimero.mero.TokenData
// 1. Launch the hosted login (opens a Custom Tab).
SsoLauncher.launch(
context,
nodeUrl = "https://your-node.example",
options = AuthLoginOptions(callbackUrl = "myapp://auth-callback", mode = "login",
permissions = listOf("admin")),
)
// 2. In the deep-link Activity that receives myapp://auth-callback:
val callback = Mero.parseAuthCallback(intent.dataString ?: "")
if (callback != null) {
mero.setTokenData(
TokenData(callback.accessToken, callback.refreshToken, expiresAt = 0L)
)
// callback also carries applicationId, contextId, contextIdentity, nodeUrl
}

parseAuthCallback reads the URL hash fragment (access_token, refresh_token, application_id, context_id, context_identity, node_url) and returns null if no access token is present. Passing expiresAt = 0L tells setTokenData to derive the real expiry from the JWT. Both SSO helpers are also standalone functions in com.calimero.mero.auth.

Pass a tokenStore in the config so tokens survive app launches. EncryptedPrefsTokenStore (AndroidX Security, hardware-backed key) is the secure default; the memory store is ephemeral.

// Secure, persisted across launches — AndroidX EncryptedSharedPreferences
val mero = Mero(
MeroConfig(
baseUrl = "https://your-node.example",
tokenStore = EncryptedPrefsTokenStore(context),
)
)
// Isolate multiple nodes with distinct files:
val store = EncryptedPrefsTokenStore(context, fileName = "mero_tokens_prod")
// Ephemeral (the default when tokenStore is null)
val ephemeral = Mero(
MeroConfig(baseUrl = "https://your-node.example", tokenStore = MemoryTokenStore())
)

Implement TokenStore (getTokens / setTokens / clear, all synchronous) to back tokens with anything else — see the reference.

mero.isAuthenticated // Boolean — is a bundle present?
mero.getTokenData() // TokenData? (debugging / handoff)
mero.clearToken() // drop the bundle + clear the store (local only)
mero.logout() // clearToken() + best-effort server revoke