WebAuthForContracts
in package
Implements SEP-45 Web Authentication for Contract Accounts protocol
This class provides a complete implementation of the SEP-45 (Stellar Web Authentication for Contract Accounts) protocol, which enables wallets and clients to prove they control a Soroban contract account by signing authorization entries provided by an anchor's authentication server.
SEP-45 is specifically for contract accounts (C... addresses). For traditional Stellar accounts (G... and M... addresses), use SEP-10 (WebAuth class) instead.
The authentication flow consists of three steps:
- Request authorization entries from the server (challenge)
- Validate and sign the entries with the client's private key(s)
- Submit the signed entries back to the server to receive a JWT token
The JWT token can then be used to authenticate subsequent requests to other SEP services such as SEP-24 (hosted deposits/withdrawals), SEP-31 (cross-border payments), or SEP-12 (KYC). The token typically has a limited validity period.
This implementation supports contract accounts and client domain verification for non-custodial wallets.
SECURITY WARNINGS:
-
Contract Address Validation (CRITICAL): Always verify that the contract_address in all authorization entries matches the WEB_AUTH_CONTRACT_ID from stellar.toml. This ensures the authorization is for the correct web authentication contract and prevents substitution attacks.
-
Sub-Invocations Check (CRITICAL): Always verify that no authorization entry contains sub-invocations. Sub-invocations could authorize additional unintended contract operations beyond authentication.
-
Server Signature Verification (CRITICAL): Always verify the challenge is signed by the server's signing key from stellar.toml. This prevents man-in-the-middle attacks where an attacker intercepts the authentication flow and provides a fake challenge to capture client signatures.
-
Function Name Validation (CRITICAL): Verify the function name is exactly "web_auth_verify". Any other function could perform unintended operations.
-
Args Validation (HIGH): Verify all function arguments match expected values including account, home_domain, web_auth_domain, web_auth_domain_account, and nonce. Inconsistencies could indicate a malicious or malformed challenge.
-
Nonce Consistency (HIGH): Verify the nonce is consistent across all authorization entries and unique. The nonce provides replay protection.
-
Signature Expiration Ledger (HIGH): Set appropriate signature expiration ledger when signing to limit the validity window. This provides time-based replay protection.
-
Network Passphrase Validation (HIGH): When the challenge response includes a network_passphrase field, validate it matches the expected network. This prevents cross-network authentication attacks where signatures from one network could be replayed on another network.
-
JWT Token Security (HIGH): Store JWT tokens securely and never expose them in logs, URLs, or insecure storage. Tokens grant access to authenticated services and should be treated as credentials. Use HTTPS for all requests with tokens.
-
Network Passphrase Consistency: Use the correct network passphrase (testnet or pubnet) when signing. Mixing passphrases can lead to signature validation failures or security vulnerabilities.
Tags
Table of Contents
Methods
- __construct() : mixed
- Constructor.
- decodeAuthorizationEntries() : array<string|int, SorobanAuthorizationEntry>
- Decodes authorization entries from base64 XDR.
- fromDomain() : WebAuthForContracts
- Creates a WebAuthForContracts instance by loading the needed data from the stellar.toml file hosted on the given domain.
- getChallenge() : ContractChallengeResponse
- Requests a challenge from the authentication server.
- jwtToken() : string
- Executes the complete SEP-45 authentication flow.
- sendSignedChallenge() : string
- Submits signed authorization entries to obtain a JWT token.
- setMockHandler() : void
- Sets a mock HTTP handler for testing purposes.
- setUseFormUrlEncoded() : void
- Sets whether to use application/x-www-form-urlencoded when submitting challenges.
- signAuthorizationEntries() : array<string|int, SorobanAuthorizationEntry>
- Signs the authorization entries for the client account.
- validateChallenge() : void
- Validates the authorization entries from the challenge response.
Methods
__construct()
Constructor.
public
__construct(string $authEndpoint, string $webAuthContractId, string $serverSigningKey, string $serverHomeDomain, Network $network[, Client|null $httpClient = null ][, string|null $sorobanRpcUrl = null ]) : mixed
Parameters
- $authEndpoint : string
-
WEB_AUTH_FOR_CONTRACTS_ENDPOINT from stellar.toml
- $webAuthContractId : string
-
WEB_AUTH_CONTRACT_ID from stellar.toml (C... address)
- $serverSigningKey : string
-
SIGNING_KEY from stellar.toml (G... address)
- $serverHomeDomain : string
-
The server home domain where the stellar.toml was loaded from
- $network : Network
-
The network used (testnet or pubnet)
- $httpClient : Client|null = null
-
Optional HTTP client to be used for requests
- $sorobanRpcUrl : string|null = null
-
Optional Soroban RPC URL. If not provided, defaults to testnet or public network URL based on the network parameter.
Tags
decodeAuthorizationEntries()
Decodes authorization entries from base64 XDR.
public
decodeAuthorizationEntries(string $base64Xdr) : array<string|int, SorobanAuthorizationEntry>
Parameters
- $base64Xdr : string
-
Base64-encoded XDR array of SorobanAuthorizationEntry
Tags
Return values
array<string|int, SorobanAuthorizationEntry> —Decoded authorization entries
fromDomain()
Creates a WebAuthForContracts instance by loading the needed data from the stellar.toml file hosted on the given domain.
public
static fromDomain(string $domain, Network $network[, Client|null $httpClient = null ]) : WebAuthForContracts
Example: fromDomain("soneso.com", Network::testnet())
Parameters
- $domain : string
-
The domain from which to get the stellar information
- $network : Network
-
The network used (testnet or pubnet)
- $httpClient : Client|null = null
-
Optional HTTP client to be used for requests
Tags
Return values
WebAuthForContracts —configured instance
getChallenge()
Requests a challenge from the authentication server.
public
getChallenge(string $clientAccountId[, string|null $homeDomain = null ][, string|null $clientDomain = null ]) : ContractChallengeResponse
Parameters
- $clientAccountId : string
-
Contract account (C...) to authenticate
- $homeDomain : string|null = null
-
Optional home domain for the request. If not provided, defaults to the server home domain from stellar.toml.
- $clientDomain : string|null = null
-
Optional client domain
Tags
Return values
ContractChallengeResponse —The challenge response
jwtToken()
Executes the complete SEP-45 authentication flow.
public
jwtToken(string $clientAccountId, array<string|int, KeyPair> $signers[, string|null $homeDomain = null ][, string|null $clientDomain = null ][, KeyPair|null $clientDomainKeyPair = null ][, callable|null $clientDomainSigningCallback = null ][, int|null $signatureExpirationLedger = null ]) : string
This method:
- Requests a challenge from the server
- Validates the authorization entries
- Signs the client entry with provided signers
- Submits the signed entries to obtain a JWT token
Parameters
- $clientAccountId : string
-
Contract account (C...) to authenticate
- $signers : array<string|int, KeyPair>
-
Keypairs to sign the client authorization entry. For contracts that implement __check_auth with signature verification, provide the keypairs with sufficient weight to meet the contract's authentication requirements. Can be empty for contracts whose __check_auth implementation does not require signatures (per SEP-45).
- $homeDomain : string|null = null
-
Optional home domain for the challenge request. If not provided, defaults to the server home domain from stellar.toml.
- $clientDomain : string|null = null
-
Optional client domain for verification
- $clientDomainKeyPair : KeyPair|null = null
-
Optional keypair for client domain signing
- $clientDomainSigningCallback : callable|null = null
-
Optional callback for remote client domain signing. Callback signature: function(array $authEntries): array
- $signatureExpirationLedger : int|null = null
-
Optional expiration ledger for signatures (for replay protection). If null and signers are provided, automatically set to current ledger + 10 (approximately 50-60 seconds). If signers array is empty, this parameter is ignored. Per SEP-45, should be set to a near-future ledger for replay protection when signatures are required.
Tags
Return values
string —JWT token that can be used to authenticate requests to protected services
sendSignedChallenge()
Submits signed authorization entries to obtain a JWT token.
public
sendSignedChallenge(array<string|int, SorobanAuthorizationEntry> $signedEntries) : string
Parameters
- $signedEntries : array<string|int, SorobanAuthorizationEntry>
-
Signed entries
Tags
Return values
string —JWT token
setMockHandler()
Sets a mock HTTP handler for testing purposes.
public
setMockHandler(MockHandler $handler) : void
Replaces the HTTP client with one using the provided mock handler. This allows tests to simulate authentication server responses without making actual HTTP requests.
Parameters
- $handler : MockHandler
-
Guzzle mock handler with predefined responses
setUseFormUrlEncoded()
Sets whether to use application/x-www-form-urlencoded when submitting challenges.
public
setUseFormUrlEncoded(bool $useFormUrlEncoded) : void
By default, application/json is used.
Parameters
- $useFormUrlEncoded : bool
-
true to use form-urlencoded, false for JSON
signAuthorizationEntries()
Signs the authorization entries for the client account.
public
signAuthorizationEntries(array<string|int, SorobanAuthorizationEntry> $authEntries, string $clientAccountId, array<string|int, KeyPair> $signers, int|null $signatureExpirationLedger[, KeyPair|null $clientDomainKeyPair = null ][, callable|null $clientDomainSigningCallback = null ][, string|null $clientDomainAccountId = null ]) : array<string|int, SorobanAuthorizationEntry>
Parameters
- $authEntries : array<string|int, SorobanAuthorizationEntry>
-
Entries to sign
- $clientAccountId : string
-
Client account to sign for
- $signers : array<string|int, KeyPair>
-
Keypairs to sign the client authorization entry. For contracts that implement __check_auth with signature verification, provide the keypairs with sufficient weight to meet the contract's authentication requirements. Can be empty for contracts whose __check_auth implementation does not require signatures (per SEP-45).
- $signatureExpirationLedger : int|null
-
Expiration ledger for signatures. Required if signers array is not empty. Ignored if signers array is empty.
- $clientDomainKeyPair : KeyPair|null = null
-
Optional client domain keypair for local signing
- $clientDomainSigningCallback : callable|null = null
-
Optional callback for remote signing. The callback receives a single SorobanAuthorizationEntry (the client domain entry) and must return a signed SorobanAuthorizationEntry.
- $clientDomainAccountId : string|null = null
-
Client domain account ID (required if callback is provided)
Tags
Return values
array<string|int, SorobanAuthorizationEntry> —Signed entries
validateChallenge()
Validates the authorization entries from the challenge response.
public
validateChallenge(array<string|int, SorobanAuthorizationEntry> $authEntries, string $clientAccountId[, string|null $homeDomain = null ][, string|null $clientDomainAccountId = null ]) : void
Validation steps:
- Each entry has no sub-invocations
- contract_address matches WEB_AUTH_CONTRACT_ID
- function_name is "web_auth_verify"
- Args validation (account, home_domain, web_auth_domain, nonce, etc.)
- Server entry exists and has valid signature
- Client entry exists
Parameters
- $authEntries : array<string|int, SorobanAuthorizationEntry>
-
Entries to validate
- $clientAccountId : string
-
Expected client account
- $homeDomain : string|null = null
-
Optional expected home domain. If not provided, defaults to the server home domain from stellar.toml.
- $clientDomainAccountId : string|null = null
-
Expected client domain account