Platform-specific guide for configuring WebAuthn passkey authentication in browser applications using the KMP Stellar SDK Smart Account Kit.
- A modern browser with WebAuthn support (Chrome 67+, Firefox 60+, Safari 14+, Edge 79+)
- HTTPS (required for WebAuthn;
localhostis the only exception)
No additional library dependencies are needed. WebAuthn is a built-in browser API (navigator.credentials).
The rpId parameter determines which domain the passkey is bound to. It must match the current page's domain or a registrable suffix of it.
Rules:
- If your app is hosted at
app.example.com, validrpIdvalues are"app.example.com"or"example.com" - Setting
rpIdto"example.com"allows passkeys to work across all subdomains (app.example.com,www.example.com, etc.) - The
rpIdcannot be a public suffix (e.g.,"com","co.uk") - For
localhostdevelopment, setrpIdto"localhost"
JsWebAuthnProvider calls the browser's navigator.credentials.create() and navigator.credentials.get() APIs for passkey creation and assertion.
Constructor parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
rpId |
String |
Yes | - | Relying party domain (must match current origin or registrable suffix) |
rpName |
String |
Yes | - | Display name shown during passkey prompts |
timeout |
Long |
No | 60000 | Timeout in milliseconds |
import com.soneso.stellar.sdk.smartaccount.JsWebAuthnProvider
val webauthnProvider = JsWebAuthnProvider(
rpId = "your-domain.com",
rpName = "My Stellar Wallet"
)
JsWebAuthnProvideris browser-only. In Node.js environments wherenavigator.credentialsis not available, it throwsWebAuthnException.NotSupported.
Structured browser storage with indexing, large storage limits, and async operations. Recommended for production.
Constructor parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dbName |
String |
No | "stellar_smart_account" |
IndexedDB database name |
import com.soneso.stellar.sdk.smartaccount.IndexedDBStorageAdapter
val storage = IndexedDBStorageAdapter()
// Or with a custom database name
val storage = IndexedDBStorageAdapter(dbName = "my_app_smart_account")Call storage.close() from a coroutine when the adapter is no longer needed to release the database connection. The adapter reopens the connection automatically on the next operation.
Synchronous key-value storage using window.localStorage. Simpler but limited to approximately 5 MB per origin. Data is not encrypted.
Constructor parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
keyPrefix |
String |
No | "stellar_sa_" |
Prefix for all localStorage keys |
import com.soneso.stellar.sdk.smartaccount.LocalStorageAdapter
val storage = LocalStorageAdapter()
// Or with a custom prefix
val storage = LocalStorageAdapter(keyPrefix = "my_app_sa_")Use IndexedDBStorageAdapter for production. LocalStorageAdapter is appropriate for prototyping or environments where IndexedDB is unavailable.
import com.soneso.stellar.sdk.smartaccount.IndexedDBStorageAdapter
import com.soneso.stellar.sdk.smartaccount.JsWebAuthnProvider
import com.soneso.stellar.sdk.smartaccount.oz.OZSmartAccountConfig
import com.soneso.stellar.sdk.smartaccount.oz.OZSmartAccountKit
val webauthnProvider = JsWebAuthnProvider(
rpId = "your-domain.com",
rpName = "My Stellar Wallet"
)
val storage = IndexedDBStorageAdapter()
val config = OZSmartAccountConfig(
rpcUrl = "https://soroban-testnet.stellar.org",
networkPassphrase = "Test SDF Network ; September 2015",
accountWasmHash = "your-wasm-hash-hex",
webauthnVerifierAddress = "<C-address of the WebAuthn verifier>",
webauthnProvider = webauthnProvider,
storage = storage
)
val kit = OZSmartAccountKit.create(config)WebAuthn requires a secure context. The browser will reject passkey operations on HTTP origins (except localhost).
For local development:
http://localhost:8080works withrpId = "localhost"http://127.0.0.1:8080does not work (WebAuthn treats127.0.0.1differently fromlocalhost)- Use a local HTTPS proxy (e.g., mkcert) for testing with custom domains
// Development configuration
val webauthnProvider = JsWebAuthnProvider(
rpId = "localhost",
rpName = "Dev Stellar Wallet"
)
val config = OZSmartAccountConfig(
rpcUrl = "https://soroban-testnet.stellar.org",
networkPassphrase = "Test SDF Network ; September 2015",
accountWasmHash = "your-wasm-hash-hex",
webauthnVerifierAddress = "<C-address of the WebAuthn verifier>",
webauthnProvider = webauthnProvider
)Passkeys created with rpId = "localhost" are only usable on localhost. Create separate passkeys for each environment.
The rpId must match the current page's domain or a registrable suffix. If your page is at https://app.example.com:
rpId = "app.example.com"-- validrpId = "example.com"-- valid (registrable suffix)rpId = "other.com"-- SecurityError
The user dismissed the passkey dialog or the browser blocked the request. The SDK maps this to WebAuthnException.Cancelled. Common causes:
- User clicked "Cancel" on the browser's passkey prompt
- The page lost focus during the ceremony
- A browser extension blocked the request
JsWebAuthnProvider requires navigator.credentials, which is not available in Node.js. WebAuthn operations are browser-only. For server-side testing, use mock implementations of the WebAuthnProvider interface.
WebAuthn does not work in cross-origin iframes by default. If your app runs inside an iframe:
- The embedding page must include
allow="publickey-credentials-create; publickey-credentials-get"on the iframe element - The embedding page's own
Permissions-Policymust not deny those features
IndexedDBStorageAdapter throws StorageException.ReadFailed if IndexedDB is unavailable (e.g., in some private browsing modes). The database opens lazily on the first storage operation, not at construction, so probe it before relying on it:
val indexedDb = IndexedDBStorageAdapter()
val storage = try {
indexedDb.getAll() // forces the database open
indexedDb
} catch (e: Exception) {
LocalStorageAdapter()
}Passkey sync depends on the browser and platform:
- Chrome: Syncs via Google Password Manager when signed in
- Safari: Syncs via iCloud Keychain
- Firefox: Varies by version and OS; check current Firefox documentation
Ensure the rpId is identical across all environments where passkeys should be shared.