WebAuth

class WebAuth(val authEndpoint: String, val network: Network, val serverSigningKey: String, val serverHomeDomain: String, val gracePeriodSeconds: Int = 300, httpClient: HttpClient? = null, httpRequestHeaders: Map<String, String>? = null, val clientDomainSigningDelegate: ClientDomainSigningDelegate? = null)

SEP-10 Web Authentication client.

Provides secure authentication for Stellar wallets and applications using a challenge-response protocol with transaction signing. This implements the client side of the SEP-10 specification for authenticating with Stellar services.

SEP-10 Authentication Flow:

  1. Client requests a challenge transaction from the authentication server

  2. Server generates a transaction with specific security requirements and signs it

  3. Client validates the challenge transaction (13 security checks)

  4. Client signs the validated transaction with their keypair(s)

  5. Client submits the signed transaction back to the server

  6. Server verifies the signatures and returns a JWT token

  7. Client uses the JWT token to authenticate subsequent API requests

The JWT token can be used to authenticate requests to SEP-6 (Deposit/Withdrawal), SEP-12 (KYC), SEP-24 (Hosted Deposit/Withdrawal), SEP-31 (Cross-Border Payments), and other Stellar services that require authentication.

Usage Patterns:

High-Level API (Recommended for most use cases):

// Initialize from domain's stellar.toml
val webAuth = WebAuth.fromDomain("example.com", Network.PUBLIC)

// One-line authentication
val authToken = webAuth.jwtToken(
clientAccountId = userKeyPair.getAccountId(),
signers = listOf(userKeyPair)
)

// Use token in API requests
httpClient.get("https://example.com/api/account") {
header("Authorization", "Bearer ${authToken.token}")
}

Low-Level API (For custom flows or debugging):

val webAuth = WebAuth.fromDomain("example.com", Network.PUBLIC)

// Step 1: Request challenge
val challenge = webAuth.getChallenge(clientAccountId = accountId)

// Step 2: Validate challenge (critical security step)
webAuth.validateChallenge(
challengeXdr = challenge.transaction,
clientAccountId = accountId
)

// Step 3: Sign challenge
val signedChallenge = webAuth.signTransaction(
challengeXdr = challenge.transaction,
signers = listOf(userKeyPair)
)

// Step 4: Submit and get token
val authToken = webAuth.sendSignedChallenge(signedChallenge)

Multi-Signature Accounts:

val webAuth = WebAuth.fromDomain("example.com", Network.PUBLIC)

// Provide all required signers for a multi-sig account
val authToken = webAuth.jwtToken(
clientAccountId = multiSigAccountId,
signers = listOf(signer1KeyPair, signer2KeyPair, signer3KeyPair)
)

Account with Memo (Sub-accounts):

// For custodial services with sub-accounts identified by memo
val authToken = webAuth.jwtToken(
clientAccountId = custodialAccountId,
signers = listOf(userKeyPair),
memo = 12345 // User's memo ID
)

Muxed Account (Modern Sub-accounts):

// For muxed accounts (M... addresses)
val authToken = webAuth.jwtToken(
clientAccountId = "M...", // Muxed account address
signers = listOf(userKeyPair)
)

Client Domain Verification (Local Signing):

// When your application wants to prove domain ownership to the server
val authToken = webAuth.jwtToken(
clientAccountId = userAccountId,
signers = listOf(userKeyPair),
clientDomain = "wallet.mycompany.com",
clientDomainKeyPair = clientDomainSigningKey
)

Client Domain Verification (Wallet Backend Signing):

// When your wallet's domain key is managed by your backend server
val signingDelegate = ClientDomainSigningDelegate { transactionXdr ->
// Send to wallet backend for domain signing
val response = httpClient.post("https://backend.wallet.com/sign") {
setBody(SignRequest(transactionXdr))
}
response.body<SignResponse>().signedTransaction
}

val authToken = webAuth.jwtToken(
clientAccountId = userAccountId,
signers = listOf(userKeyPair),
clientDomain = "wallet.mycompany.com",
clientDomainSigningDelegate = signingDelegate
)

Security Considerations:

  • Always validate the challenge before signing (done automatically in jwtToken())

  • Verify HTTPS is used for all communication with the authentication endpoint

  • Verify the server's signing key matches the stellar.toml SIGNING_KEY

  • Never skip validation checks (all 13 checks are required)

  • Store JWT tokens securely (encrypted storage, not in logs)

  • Check token expiration before use

  • Use fromDomain to automatically discover and verify server configuration

Time Bounds and Grace Period:

  • Challenge transactions have time bounds to prevent replay attacks

  • The grace period (default 300 seconds / 5 minutes) accounts for:

  • Network latency between client and server

  • Clock skew between client and server

  • Time for user to review and sign the challenge

  • Challenges are typically valid for 15 minutes

  • If validation fails due to time bounds, request a fresh challenge

See also:

Constructors

Link copied to clipboard
constructor(authEndpoint: String, network: Network, serverSigningKey: String, serverHomeDomain: String, gracePeriodSeconds: Int = 300, httpClient: HttpClient? = null, httpRequestHeaders: Map<String, String>? = null, clientDomainSigningDelegate: ClientDomainSigningDelegate? = null)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

The authentication endpoint URL (from stellar.toml WEB_AUTH_ENDPOINT)

Link copied to clipboard

Optional delegate for external client domain signing

Link copied to clipboard

Grace period for time bounds validation (default: 300 seconds)

Link copied to clipboard

The Stellar network (Network.PUBLIC or Network.TESTNET)

Link copied to clipboard

The home domain of the server (used in challenge validation)

Link copied to clipboard

The server's public signing key (from stellar.toml SIGNING_KEY)

Functions

Link copied to clipboard
suspend fun getChallenge(clientAccountId: String, memo: Long? = null, homeDomain: String? = null, clientDomain: String? = null): ChallengeResponse

Requests a challenge transaction from the authentication server.

Link copied to clipboard
suspend fun jwtToken(clientAccountId: String, signers: List<KeyPair>, memo: Long? = null, homeDomain: String? = null, clientDomain: String? = null, clientDomainKeyPair: KeyPair? = null, clientDomainSigningDelegate: ClientDomainSigningDelegate? = null): AuthToken

Performs complete SEP-10 authentication flow.

Link copied to clipboard
suspend fun sendSignedChallenge(signedChallengeXdr: String): AuthToken

Submits a signed challenge transaction to obtain JWT token.

Link copied to clipboard
suspend fun signTransaction(challengeXdr: String, signers: List<KeyPair>, clientDomainKeyPair: KeyPair? = null, clientDomainSigningDelegate: ClientDomainSigningDelegate? = null): String

Signs a challenge transaction with provided keypairs.

Link copied to clipboard
suspend fun validateChallenge(challengeXdr: String, clientAccountId: String, clientDomainAccountId: String? = null, expectedMemo: Long? = null)

Validates a challenge transaction according to SEP-10 security requirements.