Skip to content

Authentication

Every call MeroKit 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.

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

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

let mero = Mero(config: MeroConfig(
baseURL: URL(string: "https://your-node.example")!,
credentials: Credentials(username: "alice", password: "s3cr3t")
))
try await mero.authenticate() // uses config.credentials

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.

Because Mero is an actor, concurrent requests that all hit a 401 share one in-flight refresh. And because refresh tokens are single-use (rotated on every refresh), the SDK re-reads the token store first — so if another process or app extension already rotated the token, that newer bundle is adopted instead of replaying a consumed one (which would revoke the whole token family).

For hosted login, build the node’s login URL, present it in ASWebAuthenticationSession, and feed the callback URL back in — the native analog of the web redirect flow.

import AuthenticationServices
// 1. Build the login URL.
let loginURL = Mero.buildAuthLoginUrl(
nodeUrl: "https://your-node.example",
options: AuthLoginOptions(callbackUrl: "myapp://auth-callback", mode: "login",
permissions: ["admin"])
)
// 2. Present it (ASWebAuthenticationSession), then on the callback URL:
if let callback = Mero.parseAuthCallback(callbackURL.absoluteString) {
await mero.setTokenData(from: callback)
// 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 nil if no access token is present. Both SSO helpers are static on Mero.

Pass a tokenStore in the config so tokens survive app launches. The Keychain store is the secure default; the memory store is ephemeral.

// Secure, persisted across launches — backed by the iOS/macOS Keychain
let mero = Mero(config: MeroConfig(
baseURL: URL(string: "https://your-node.example")!,
tokenStore: KeychainTokenStore()
))
// Share tokens with an app extension via a Keychain access group:
let shared = KeychainTokenStore(accessGroup: "TEAMID.group.myapp")
// Ephemeral (the default when tokenStore is nil)
let ephemeral = Mero(config: MeroConfig(
baseURL: URL(string: "https://your-node.example")!,
tokenStore: MemoryTokenStore()
))

Conform to TokenStore (getTokens / setTokens / clear) to back tokens with anything else — see the reference.

await mero.isAuthenticated // Bool — is a bundle present?
await mero.currentTokenData() // TokenData? (debugging / handoff)
await mero.clearToken() // drop the bundle + clear the store
await mero.logout() // clearToken() + close() — full sign-out