Stellar PHP SDK API Documentation

WebAuthForContracts

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:

  1. Request authorization entries from the server (challenge)
  2. Validate and sign the entries with the client's private key(s)
  3. 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:

  1. 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.

  2. Sub-Invocations Check (CRITICAL): Always verify that no authorization entry contains sub-invocations. Sub-invocations could authorize additional unintended contract operations beyond authentication.

  3. 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.

  4. Function Name Validation (CRITICAL): Verify the function name is exactly "web_auth_verify". Any other function could perform unintended operations.

  5. 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.

  6. Nonce Consistency (HIGH): Verify the nonce is consistent across all authorization entries and unique. The nonce provides replay protection.

  7. Signature Expiration Ledger (HIGH): Set appropriate signature expiration ledger when signing to limit the validity window. This provides time-based replay protection.

  8. 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.

  9. 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.

  10. 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
see
https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0045.md

SEP-45 Specification

see
StellarToml

For discovering the auth endpoint

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
throws
InvalidArgumentException

if any parameter is invalid

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
throws
Exception

if required fields are missing from stellar.toml

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
throws
ContractChallengeRequestErrorResponse

on request failure

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:

  1. Requests a challenge from the server
  2. Validates the authorization entries
  3. Signs the client entry with provided signers
  4. 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
throws
ContractChallengeValidationError
throws
ContractChallengeValidationErrorInvalidAccount
throws
ContractChallengeValidationErrorInvalidArgs
throws
ContractChallengeValidationErrorInvalidContractAddress
throws
ContractChallengeValidationErrorInvalidFunctionName
throws
ContractChallengeValidationErrorInvalidHomeDomain
throws
ContractChallengeValidationErrorInvalidNonce
throws
ContractChallengeValidationErrorInvalidServerSignature
throws
ContractChallengeValidationErrorInvalidWebAuthDomain
throws
ContractChallengeValidationErrorInvalidNetworkPassphrase
throws
ContractChallengeValidationErrorMissingClientEntry
throws
ContractChallengeValidationErrorMissingServerEntry
throws
ContractChallengeValidationErrorSubInvocationsFound
throws
ContractChallengeRequestErrorResponse
throws
SubmitContractChallengeErrorResponseException
throws
SubmitContractChallengeTimeoutResponseException
throws
SubmitContractChallengeUnknownResponseException
throws
GuzzleException
throws
InvalidArgumentException
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
throws
SubmitContractChallengeErrorResponseException

on error

throws
SubmitContractChallengeTimeoutResponseException

on timeout

throws
SubmitContractChallengeUnknownResponseException

on unknown response

throws
GuzzleException
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
throws
RuntimeException

if no address credentials are found in entry

throws
InvalidArgumentException

if callback validation fails or client domain entry not found

throws
Exception

if credentials address could not be converted to StrKey representation

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:

  1. Each entry has no sub-invocations
  2. contract_address matches WEB_AUTH_CONTRACT_ID
  3. function_name is "web_auth_verify"
  4. Args validation (account, home_domain, web_auth_domain, nonce, etc.)
  5. Server entry exists and has valid signature
  6. 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

Tags
throws
ContractChallengeValidationError

on validation failure

throws
ContractChallengeValidationErrorInvalidAccount
throws
ContractChallengeValidationErrorInvalidArgs
throws
ContractChallengeValidationErrorInvalidContractAddress
throws
ContractChallengeValidationErrorInvalidFunctionName
throws
ContractChallengeValidationErrorInvalidHomeDomain
throws
ContractChallengeValidationErrorInvalidNonce
throws
ContractChallengeValidationErrorInvalidServerSignature
throws
ContractChallengeValidationErrorInvalidWebAuthDomain
throws
ContractChallengeValidationErrorMissingClientEntry
throws
ContractChallengeValidationErrorMissingServerEntry
throws
ContractChallengeValidationErrorSubInvocationsFound

        
On this page

Search results