Sign-In with Ethereum (SIWE)

repository·main·Indexed 22 days ago

https://github.com/spruceid/siwe

A standard for Ethereum accounts to authenticate with off-chain services by signing a structured message format (EIP-4361). The library provides the SiweMessage class for creating and formatting messages, the ParsedMessage class for ABNF grammar parsing, and verification methods supporting EIP-191 and EIP-1271 contract wallets. It also includes the siwe-root package containing official brand assets and buttons.

Tokens
3.4K
Snippets
8
Records
13
Agent score
76%

What's inside siwe

  1. Use Sign-In with Ethereum brand assets

    main
    The siwe-root package provides official Sign-In with Ethereum brand assets, including the logo, logomark, and icons. These assets are released under a CC0 1.0 license, meaning they are free for public use and incorporation into any project.
  2. Quickstart examples for Sign-In with Ethereum

    main

    To get started with implementing Sign-In with Ethereum, you can explore several specialized quickstart repositories that demonstrate different parts of the authentication flow:

    • Node.js: Basic printing/logic examples.
    • Frontend: Client-side implementation.
    • Backend: Server-side verification logic.
    • End to end: A complete application demonstrating the full flow.
    • Sign-In with Ethereum Notepad: A practical application example.
  3. Install and build the SIWE monorepo

    main

    If you are working within the monorepo environment, use the following commands to set up your development environment:

    1. Install dependencies: npm install
    2. Build the library: npm run build
    3. Run all tests: npm run test

    Development can be performed at the individual package/* level, where tests can be run for each specific package.

    npm install
    npm run build
    npm run test
  4. Incorporate Sign-In with Ethereum buttons

    main

    For projects requiring a user-facing button to initiate Sign-In with Ethereum, the asset set includes two standard button designs optimized for different UI themes:

    • Light Background: SIWE_Button_Light_BG.png
    • Dark Background: SIWE_Button_Dark_BG.png

    Designers can also access these buttons as Figma components or explore additional creative designs via the provided Figma links.

  5. How SiweMessage handles instantiation

    main

    The SiweMessage constructor is polymorphic and handles two main input types:

    1. String Input: If you pass a string, the constructor uses ParsedMessage (an ABNF parser) to validate and extract all fields from the raw SIWE message string.
    2. Object Input: If you pass a Partial<SiweMessage>, it attributes the fields directly.
      • It automatically converts chainId from a string to a number using parseIntegerNumber if necessary.
      • It automatically generates a nonce using generateNonce() if one is not provided.
      • It performs a validation check by attempting to stringify the resulting object via prepareMessage() and passing it through the parser.

    This ensures that whether you are parsing an existing message or building a new one, the resulting object is always valid according to the EIP-4361 specification.

  6. Verify a SIWE signature with verify()

    main

    The verify(params, opts) method checks the integrity of a SiweMessage by matching its signature. It supports standard EIP-191 signature recovery and EIP-1271 (contract wallet) verification.

    Parameters

    params: VerifyParams

    • signature: (Required) The signature provided by the user.
    • scheme: (Optional) The URI scheme for domain binding. Must match this.scheme if provided.
    • domain: (Optional) The domain. Must match this.domain if provided.
    • nonce: (Optional) The nonce. Must match this.nonce if provided.
    • time: (Optional) The time to use for expiration/not-before checks. Defaults to current time.

    opts: VerifyOpts

    • suppressExceptions: If true, the method returns a SiweResponse with success: false instead of throwing an error. Defaults to false.
    • provider: Used for EIP-1271 contract wallet signature verification.
    • verificationFallback: An optional function to provide custom verification logic. It receives (params, opts, siweMessage, eip1271Promise).

    Return Value

    Returns a Promise<SiweResponse>. A successful verification returns { success: true, data: SiweMessage }. A failed verification returns { success: false, data: SiweMessage, error: Error } (if suppressExceptions is true).

    const siweResponse = await message.verify(
      {
        signature: '0x...',
        domain: 'example.com',
      },
      {
        suppressExceptions: true,
        provider: myEthersProvider,
      }
    );
  7. Use the SiweMessage class to manage SIWE messages

    main

    The SiweMessage class is the primary interface for creating, formatting, and verifying Sign-In with Ethereum (EIP-4361) messages. You can instantiate it using either a raw SIWE message string or a partial configuration object.

    Key Capabilities

    • Instantiation: Create a message object from a string (parsed via ABNF) or an object.
    • Message Preparation: Generate the exact string required for EIP-191 signing using prepareMessage() or toMessage().
    • Verification: Validate a signature against the message using the verify() method.

    Fields

    • domain: The RFC 4501 DNS authority requesting the signature.
    • address: The Ethereum address performing the signing (EIP-55 checksummed).
    • chainId: The EIP-155 Chain ID.
    • nonce: A random token (at least 8 alphanumeric characters) to prevent replay attacks.
    • uri: The resource subject to the signing.
    • version: The current version of the message (e.g., '1').
    • statement: An optional human-readable ASCII assertion.
    • expirationTime / notBefore: ISO 8601 datetime strings for validity windows.
    • resources: An array of RFC 3986 URIs for the user to resolve.
    import { SiweMessage } from 'siwe';
    
    // 1. Create a message from an object
    const message = new SiweMessage({
      domain: 'example.com',
      address: '0x...',
      statement: 'Sign in with your Ethereum account',
      uri: 'https://example.com',
      version: '1',
      chainId: 1,
      nonce: 'random-nonce-string',
    });
    
    // 2. Get the string to be signed by the user
    const textToSign = message.prepareMessage();
    
    // 3. Verify the signature after the user signs it
    const siweResponse = await message.verify({
      signature: '0x...',
      // optional: scheme, domain, nonce, time
    });
    
    if (siweResponse.success) {
      console.log('Verified!', siweResponse.data);
    } else {
      console.error('Verification failed:', siweResponse.error);
    }
  8. Validate a URI with isUri()

    main

    The isUri function checks if a given string conforms to the expected URI format used within the SIWE context. It returns true if the string is a valid URI according to the internal grammar, and false otherwise.

    import { isUri } from '@spruceid/siwe-parser';
    
    const valid = isUri('siwe://example.com/path?query=1');
    const invalid = isUri('not-a-uri');
    
    console.log(valid);   // true
    console.log(invalid); // false
  9. Parse SIWE messages with ParsedMessage

    main

    The ParsedMessage class is used to parse a raw Sign-In with Ethereum (SIWE) message string into a structured object. When instantiated with a message string, it performs ABNF grammar parsing. If the message is invalid according to the SIWE specification, the constructor will throw an Error containing the parsing errors.

    Upon successful parsing, the resulting object contains the following fields:

    • scheme: The URI scheme (e.g., siwe).
    • domain: The domain of the SIWE message.
    • address: The Ethereum address.
    • statement: The human-readable message statement.
    • uri: The full URI.
    • version: The SIWE version.
    • chainId: The network chain ID as a number.
    • nonce: The unique nonce.
    • issuedAt: The issuance timestamp.
    • expirationTime: The expiration timestamp (optional).
    • notBefore: The 'not before' timestamp (optional).
    • requestId: The request ID (optional).
    • resources: An array of resource URIs (optional).
    • uriElements: An object containing decomposed URI components:
      • scheme
      • userinfo (optional)
      • host (optional)
      • port (optional)
      • path
      • query (optional)
      • fragment (optional)
    import { ParsedMessage } from '@spruceid/siwe-parser';
    
    try {
      const msg = "siwe://example.com/\nDomain: example.com\nAddress: 0x...\n...";
      const parsed = new ParsedMessage(msg);
      console.log(parsed.address);
      console.log(parsed.chainId);
    } catch (e) {
      console.error("Failed to parse SIWE message:", e.message);
    }
  10. Configure verification options with VerifyOpts

    main

    The VerifyOpts interface allows you to customize the verification process, such as enabling EIP-1271 support or providing custom fallback logic.

    Fields:

    • provider (optional): An ethers provider used for EIP-1271 smart contract wallet validation.
    • suppressExceptions (optional): If set to true, the library will not throw exceptions on errors (instead, errors are returned in the SiweResponse). Defaults to false.
    • verificationFallback (optional): A custom async function that runs alongside the EIP-1271 check. It receives the params, opts, the original message, and a EIP1271Promise which can be awaited to get the result of the standard EIP-1271 check.
    const opts: VerifyOpts = {
      provider: myEthersProvider,
      suppressExceptions: true,
      verificationFallback: async (params, opts, message, EIP1271Promise) => {
        const eip1271Result = await EIP1271Promise;
        if (eip1271Result.success) return eip1271Result;
        // Implement custom logic here
        return { success: false, error: new SiweError('Custom error'), data: message };
      }
    };
  11. Configure verification parameters with VerifyParams

    main

    When verifying a SIWE message, you must provide a VerifyParams object. This object contains the signature and optional metadata used to validate the authenticity and context of the request.

    Fields:

    • signature (required): The signature of the message signed by the wallet.
    • scheme (optional): The RFC 3986 URI scheme for the authority requesting the signing.
    • domain (optional): The RFC 4501 DNS authority requesting the signing.
    • nonce (optional): A randomized alphanumeric token (at least 8 characters) used to prevent replay attacks.
    • time (optional): An ISO 8601 datetime string representing the current time.
    const params: VerifyParams = {
      signature: '0x...',
      domain: 'example.com',
      nonce: 'abc123def456',
      scheme: 'https',
      time: '2025-05-30T12:00:00Z'
    };
  12. Understand SiweError types and structure

    main

    Errors in SIWE verification are encapsulated in the SiweError class. When verification fails, the SiweError provides a machine-readable type and optional context about what was expected versus what was received.

    Error Types (SiweErrorType)

    • EXPIRED_MESSAGE: expirationTime is in the past.
    • INVALID_DOMAIN: domain is empty or invalid.
    • SCHEME_MISMATCH: scheme does not match the provided verification scheme.
    • DOMAIN_MISMATCH: domain does not match the provided domain.
    • NONCE_MISMATCH: nonce does not match the provided nonce.
    • INVALID_ADDRESS: address is not a valid address or does not conform to EIP-55.
    • INVALID_URI: uri does not conform to RFC 3986.
    • INVALID_NONCE: nonce is not alphanumeric or is shorter than 8 characters.
    • NOT_YET_VALID_MESSAGE: notBefore is in the future.
    • INVALID_SIGNATURE: Signature does not match the message address.
    • INVALID_TIME_FORMAT: expirationTime, notBefore, or issuedAt are not ISO-8601 compliant.
    • INVALID_MESSAGE_VERSION: version is not 1.
    • UNABLE_TO_PARSE: Required fields are missing or the message is malformed.