# SIWE PHP > PHP library for Sign-In with Ethereum message generation, parsing and EOA/EIP-1271 signature verification. Package: iltumio/siwe-php. PHP 8.3+ with GMP, mbstring, ctype and JSON. Namespace: Iltumio\SiwePhp\SiweMessage. verify() returns a Guzzle promise. Use wait() and check success. Bind login to the configured domain and stored nonce, compare the exact stored challenge, enforce its lifetime and atomically consume it before authentication. Use trusted server-configured RPC endpoints for the permitted chain only. Constructor failures are not suppressed by verification options. The library has not undergone a formal security audit. Source: https://iltumio.github.io/siwe-php/guide/getting-started.html # Getting started SIWE PHP provides message generation, parsing and signature verification for Sign-In with Ethereum. ## Requirements - PHP 8.3 or later. - GMP, mbstring, ctype and JSON PHP extensions. - Composer for dependency installation. ```bash composer require iltumio/siwe-php ``` Load Composer's autoloader in standalone scripts: ```php require __DIR__ . '/vendor/autoload.php'; use Iltumio\SiwePhp\SiweMessage; $message = new SiweMessage([ 'domain' => 'example.com', 'address' => '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'uri' => 'https://example.com/login', 'version' => '1', 'chainId' => 1, ]); $signableMessage = $message->prepareMessage(); ``` Store the message and `$message->nonce` for the login attempt, then ask the user's wallet to sign the exact message. The library verifies signatures; it does not manage wallet connections, sessions or user records. Continue with [message generation](./messages.md) and [signature verification](./verification.md). --- Source: https://iltumio.github.io/siwe-php/guide/messages.html # Generate and parse messages ## Generate Pass an array of SIWE fields to `SiweMessage` and serialize it with `prepareMessage()`. ```php use Iltumio\SiwePhp\SiweMessage; $message = new SiweMessage([ 'domain' => 'example.com', 'address' => '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 'statement' => 'Sign in to Example.', 'uri' => 'https://example.com/login', 'version' => '1', 'chainId' => 1, 'expirationTime' => gmdate('Y-m-d\TH:i:s\Z', time() + 300), 'resources' => ['https://example.com/terms'], ]); $text = $message->prepareMessage(); $nonce = $message->nonce; ``` When omitted, the nonce is generated using cryptographically secure randomness. `issuedAt` is populated on first serialization. Set an explicit `chainId` for the wallet's chain; serialization defaults an omitted chain ID to `1`. Dates must be valid RFC 3339 timestamps. The address must pass the library's EIP-55 validation. A custom nonce must contain at least eight alphanumeric characters. See the [field reference](../reference/siwe-message.md#fields). ## Parse ```php $message = new SiweMessage($signedMessage); $address = $message->address; $chainId = $message->chainId; $resources = $message->resources; ``` Parsing validates the input and preserves resources. Malformed dates, invalid nonces, duplicate fields, line breaks in fields and trailing data are rejected. Handle constructor failures before calling `verify()`; `suppressExceptions` only applies to verification. ## Validate changes Fields are public. After changing them, call `validateMessage()` to check their values. Serialization and verification also validate the message. Keep the stored challenge unchanged once the wallet has been asked to sign it. --- Source: https://iltumio.github.io/siwe-php/guide/verification.html # Verify a signature Parse the exact message signed by the wallet, then verify it against the domain configured by your application and the nonce stored for that login attempt. ```php use Iltumio\SiwePhp\SiweMessage; try { $message = new SiweMessage($signedMessage); $result = $message->verify([ 'signature' => $signature, 'domain' => 'example.com', 'nonce' => $storedNonce, ], [ 'suppressExceptions' => true, 'providerUrl' => '', // This example accepts EOA signatures only. ])->wait(); } catch (\Throwable $error) { // Reject malformed messages and unexpected failures. throw $error; } if ($result['success']) { $address = $result['data']->address; // Atomically consume the challenge before creating the authenticated session. } else { $reason = $result['error']->getMessage(); // Reject the login attempt. } ``` `verify()` returns a Guzzle promise. Call `wait()` to execute verification and obtain its result. Successful results contain `success: true` and `data` (the `SiweMessage`). With `suppressExceptions: true`, verification failures resolve with `success: false`, `data` and `error`. Without `suppressExceptions`, the promise is rejected on failure. The rejection reason is a result array; Guzzle's `wait()` throws when unwrapping it. Using suppressed verification failures is convenient when an application needs structured error results. ## Time checks Verification rejects expired messages and messages whose `notBefore` is in the future. The default verification time is the current time. For deterministic tests, pass an RFC 3339 `time` parameter: ```php $result = $message->verify([ 'signature' => $signature, 'domain' => 'example.com', 'nonce' => $storedNonce, 'time' => '2026-01-01T12:00:00Z', ], [ 'suppressExceptions' => true, 'providerUrl' => '', ])->wait(); ``` Keep `time` under server control. The library does not automatically impose a maximum age based on `issuedAt`; enforce your application's challenge lifetime separately or set and enforce `expirationTime`. ## Signature checks EOA signatures are recovered locally. If recovery does not match the message address, the library can fall back to [EIP-1271 verification](./contract-wallets.md). Unknown verification parameter or option keys fail verification. --- Source: https://iltumio.github.io/siwe-php/guide/contract-wallets.html # Smart contract wallets SIWE PHP supports EIP-1271 contract signatures through an Ethereum JSON-RPC `eth_call`. ```php $result = $message->verify([ 'signature' => $signature, 'domain' => 'example.com', 'nonce' => $storedNonce, ], [ 'suppressExceptions' => true, 'providerUrl' => $rpcUrl, ])->wait(); ``` Choose a trusted HTTP(S) RPC endpoint for the message's chain. Select it from server-side configuration after checking that the chain is supported by your application. Do not accept arbitrary RPC URLs from the login request. The library first attempts local EOA recovery. If the recovered address does not match, it calls the wallet contract's `isValidSignature` method and requires the EIP-1271 magic value. Contract signatures do not need the standard EOA length or recovery format, but the signature parameter must be a hexadecimal string prefixed with `0x`. ## Provider behavior - An omitted `providerUrl` uses the existing default provider only for chain ID `1`. - Other chains require an explicit endpoint for contract verification. - `providerUrl: ''` disables RPC verification while keeping local EOA verification available. - HTTP requests have a 10-second total timeout and a 5-second connection timeout. - RPC errors, reverted calls and malformed responses fail verification. Configure your own provider for production availability. Contract verification depends on the chain state reported by that provider. --- Source: https://iltumio.github.io/siwe-php/guide/authentication.html # Authentication flow A valid signature proves wallet ownership for a message. Your application remains responsible for issuing challenges and establishing sessions. ## Issue a challenge 1. Validate the requested wallet address and supported chain. 2. Generate the SIWE message with the application's configured domain and URI, an unpredictable nonce and a short expiration time. 3. Store the exact serialized message, nonce, expiration and intended chain in server-side state tied to the login attempt. 4. Return the message to the client and ask the wallet to sign it unchanged. ## Complete the login 1. Require an existing, unexpired challenge belonging to the login attempt. 2. Compare the returned message with the stored challenge, including its URI, address and chain. Comparing the exact message is a straightforward way to enforce all issued fields. 3. Parse the message and verify the signature with the configured domain and stored nonce. Select any RPC endpoint from trusted server configuration. 4. Atomically consume the challenge on successful verification so concurrent requests cannot replay it. 5. Create or rotate the authenticated session and associate it with the verified address and chain as appropriate for your application. Never derive the expected domain or nonce solely from the submitted message. Passing neither to `verify()` leaves those bindings unchecked. Resources and the statement are message content; verification does not enforce application-specific authorization rules for them. ## Application responsibilities Use HTTPS, protect session cookies, apply the application's CSRF and rate-limit policies, and reject malformed input. Avoid disclosing sensitive internal errors to clients. Expire unused challenges and enforce the permitted chains and authentication URI. ::: warning Security status This library is released as is and has not undergone a formal security audit. ::: --- Source: https://iltumio.github.io/siwe-php/reference/siwe-message.html # SiweMessage ```php use Iltumio\SiwePhp\SiweMessage; ``` ## Constructor `new SiweMessage($param)` accepts an array of fields or a serialized SIWE message string. Construction parses or assigns fields and validates them. Invalid input throws; catch `\Throwable` at your request boundary. ## Fields | Field | Value and behavior | | --- | --- | | `domain` | Required authority, such as `example.com` or `example.com:3000`; no URL scheme, path or whitespace. | | `address` | Required Ethereum address passing EIP-55 validation. | | `uri` | Required valid URL identifying the authentication request. | | `version` | SIWE version `1`; use the string `'1'`. | | `chainId` | Positive integer, or a digit string normalized to an integer within PHP's integer range. Defaults to `1` on serialization if omitted. | | `nonce` | At least eight alphanumeric characters; generated securely when absent from the field array. | | `issuedAt` | RFC 3339 timestamp; populated on first serialization when omitted. | | `statement` | Optional single line of printable ASCII text. | | `expirationTime` | Optional RFC 3339 expiration timestamp. Verification fails at or after this time. | | `notBefore` | Optional RFC 3339 timestamp. Verification fails before this time. | | `requestId` | Optional request identifier using the permitted URI characters or percent escapes. | | `resources` | Array of valid resource URLs; defaults to `[]`. | Fields are public. Prefer constructing a new message to mutating a challenge that has already been issued. ## Methods | Method | Behavior | | --- | --- | | `prepareMessage()` | Returns the signable message string via `toMessage()`. | | `toMessage()` | Validates and serializes fields; populates missing serialization defaults. | | `validateMessage()` | Validates current fields and throws on invalid values. Accepts no arguments. | | `fromParsedMessage($fields)` | Assigns recognized fields and normalizes the chain ID; used internally by the constructor. Does not independently perform full validation. | | `verify(array $params, array $opts = [])` | Returns a Guzzle promise for signature and constraint verification. | ## Verification parameters | Parameter | Behavior | | --- | --- | | `signature` | Required hexadecimal signature string with `0x` prefix. | | `domain` | Optional exact comparison with the message domain; supply the application's expected domain for login. | | `nonce` | Optional exact comparison with the message nonce; supply the stored challenge nonce for login. | | `time` | Optional RFC 3339 verification time; defaults to the current time. | ## Verification options | Option | Behavior | | --- | --- | | `suppressExceptions` | When truthy, verification failures resolve to a structured failure result. Otherwise the promise is rejected. Defaults to disabled. | | `providerUrl` | HTTP(S) Ethereum RPC URL for contract verification. Empty string disables RPC; omission selects the default mainnet provider for chain ID `1` only. | Unknown parameter or option keys are rejected. `providerUrl`, when provided, must be a string. ## Results Success: `['success' => true, 'data' => $message]`. Suppressed failure: `['success' => false, 'data' => $message, 'error' => $error]`. Read `$error->getMessage()` for the reason. Constructor exceptions occur before verification and are not suppressed by this option. --- Source: https://iltumio.github.io/siwe-php/reference/development.html # Development ## PHP library ```bash composer install composer test composer audit ``` The default test suite does not depend on a live Ethereum provider. To include the mainnet contract-wallet integration fixtures: ```bash SIWE_RPC_URL=https://your-mainnet-rpc.example ./run-tests.sh ``` CI tests PHP 8.3, 8.4 and 8.5. ## Documentation The site uses [VitePress](https://vitepress.dev/) with Markdown source in `docs/`. Use Node.js 22 or later and npm: ```bash npm ci npm run docs:dev ``` Build and preview the static output: ```bash npm run docs:build npm run docs:preview ``` The build regenerates `llms.txt` and `llms-full.txt` from the Markdown guides and [agent prompts](../agents/index.md). Vite is pinned through an override to a patched 6.4 release while the site uses stable VitePress 1.6.4. The production base path is `/siwe-php/`. GitHub Actions builds documentation on pull requests and deploys changes pushed to `main` to [GitHub Pages](https://iltumio.github.io/siwe-php/). The deployment can also be triggered manually from the Actions tab. ## Credits and licenses A PHP port of [SIWE](https://github.com/spruceid/siwe). See [login.xyz](https://login.xyz). Consult the repository's [LICENSE](https://github.com/iltumio/siwe-php/blob/main/LICENSE), [MIT license](https://github.com/iltumio/siwe-php/blob/main/LICENSE-MIT) and [Apache license](https://github.com/iltumio/siwe-php/blob/main/LICENSE-APACHE) for licensing details. --- Source: https://iltumio.github.io/siwe-php/agents/index.html # Use SIWE PHP with AI agents Use these pages as context for coding agents integrating or reviewing SIWE PHP. The documentation is statically rendered, so its content is available without executing JavaScript. ## Machine-readable documentation - [llms.txt](../llms.txt): a concise index of documentation and integration constraints. - [llms-full.txt](../llms-full.txt): all guides, API reference and agent prompts in one plain-text file. These files follow the llms.txt convention; individual agents may need you to supply the URL explicitly. They do not guarantee automatic discovery by every AI tool. The full-text file is generated from the Markdown source on every build, keeping it aligned with the website. ## Integration prompt Copy this prompt into your coding agent and replace the bracketed values: ```text Integrate iltumio/siwe-php into [application/framework] using PHP 8.3+. Read https://iltumio.github.io/siwe-php/llms-full.txt first. Inspect the application's existing routes, session storage and authentication conventions. Use the documented Iltumio\SiwePhp\SiweMessage API; do not invent methods. Configured domain: [example.com] Authentication URI: [https://example.com/login] Allowed chain IDs: [1] Contract wallet support: [yes/no] Trusted RPC configuration: [server-side configuration name, never a secret value] Implement challenge issuance and verification. Store the exact message and nonce in server-side state tied to the login attempt, with a short expiration. Require the submitted message to match the stored challenge. Verify the signature with the server-configured domain and stored nonce, then atomically consume the challenge before creating or rotating the session. Validate the allowed chain and select RPC URLs only from trusted server configuration. Use providerUrl => '' if contract wallet support is disabled. Handle constructor errors separately from verification failures. verify() returns a Guzzle promise; use suppressExceptions => true and wait(), then check success before authenticating. Enforce challenge lifetime server-side; issuedAt alone does not impose a maximum age. Keep wallet connection and signing in the client; this PHP library does not provide those features. Add tests for a valid login, malformed message, changed challenge, wrong domain, wrong nonce, expiry, unsupported chain, replay and concurrent reuse. Follow the application's existing test tools. Report the implemented flow, checks run and any remaining limitations. Never claim a security audit. ``` ## Review prompt ```text Review this application's SIWE PHP authentication integration. Read https://iltumio.github.io/siwe-php/llms-full.txt and inspect the actual code. Check that issued challenges are stored server-side and submitted messages match them; domain, nonce, URI, address and chain are bound to the challenge; expiry is enforced; nonce consumption is atomic; sessions are rotated after success; RPC endpoints come from trusted configuration; parse failures and promise failures reject login; unsupported chains and replay attempts fail. Check that no session is created before successful verification and nonce consumption. Check the existing CSRF, cookie and rate-limit protections. Report findings with severity, file/line evidence and concrete fixes. Distinguish confirmed issues from unverified assumptions. Run relevant tests and state their results. Do not describe this review as a formal security audit. ``` ## Documentation maintenance prompt ```text Update SIWE PHP documentation for the current implementation. Read src/SiwePhp.php, src/utils.php, src/constants.php, composer.json and tests. Keep examples aligned with actual field validation and promise behavior. Edit the relevant Markdown under docs/; do not hand-edit generated text files. Run npm ci and npm run docs:build. Ensure llms.txt and llms-full.txt are regenerated, links resolve and examples use the documented namespace/API. Summarize behavior changes and validation results. ```