Sign In With Ethereum (SiWe)

Sign In With Ethereum (SiWe) describes the process for Ethereum accounts to authenticate with non-EVM systems. Completing an authentication attempt requires the user account to request a random challenge, sign that challenge, and then submit that challenge back to the server to validate the user owns the EOA they've presented to the mesh server

📘

Externally Owned Accounts (EOA's)

  • An EOA (Externally Owned Account) is your personal Ethereum wallet account, controlled by your private key. Think of it like your digital identity on Ethereum - it can hold funds and interact with blockchain applications. When you sign in with Ethereum, you're proving you own this account by signing a message with your private key.

Authentication Reference

The Mesh SDK authentication flow consists of four main functions that handle requesting challenges, signing messages, and managing authentication state using Sign in With Ethereum (SiWE).

SDK Methods

FunctionDescription
requestNonceRequests a random challenge from the server to be signed by the user account
invalidateNonceRotates the random challenge to expire the active challenge
signMessageSiWeSigns the SiWE message locally with the configured private key
authenticateSiWeSubmits the signed SiWE message to the Mesh API gateway

requestNonce

Requests a random challenge from the server that will be signed by the user's Ethereum account.

const response = await mesh.enforcer.requestNonce({ 
  account_address: mesh.getSigner.account.address 
});

Parameters

ParameterTypeDescription
account_addressstringThe Ethereum address requesting authentication

Response

{
  "nonce": "string" // The random challenge to be signed
}

invalidateNonce

Rotates the random challenge, forcing users to sign a new nonce for authentication.

const response = await mesh.enforcer.invalidateNonce({ 
  account_address: mesh.signer.account.address 
});

Parameters

ParameterTypeDescription
account_addressstringThe Ethereum address associated with the nonce to invalidate

Response

{
  "success": true // Indicates if nonce was successfully invalidated
}

signMessageSiWe

Signs the SiWE message locally using the configured private key.

const siweMessageParams = {
  domain: 'localhost',
  address: mesh.getSigner.account.address,
  uri: 'http://0.0.0.0',
  version: '1',
  chainId: 1,
  nonce: nonce,
  issuedAt: new Date().toISOString(),
};

const { message, signature } = await mesh.signSiWeMessage(siweMessageParams);

Parameters

ParameterTypeDescription
domainstringDomain requesting the signature
addressstringUser's Ethereum address
uristringURI of the application
version'1'SiWE version
chainIdnumberChain ID of the network
noncestringChallenge from requestNonce
issuedAtstringISO timestamp

Response

{
  "message": "string",   // Formatted SiWE message
  "signature": "string"  // Message signature
}

authenticateSiWe

Submits the signed SiWE message to the Mesh API gateway for authentication.

const response = await mesh.enforcer.authenticateSiWe({ 
  message, 
  signature 
});

Parameters

ParameterTypeDescription
messagestringSigned SiWE message
signaturestringMessage signature

Authentication Example

🔑

Authentication Flow Steps

  1. Request a nonce challenge using requestNonce
  2. Sign the challenge using signMessageSiWe
  3. Submit signed message using authenticateSiWe
  4. Optionally invalidate the nonce using invalidateNonce
import { initializeMeshSDK, MeshSDKConfig } from "./util";
import { sepolia } from 'viem/chains';

// API KEY not required after SiWe session established
const API_KEY = "";

async function logResponse(method: string, response: any) {
    console.log(`Method: ${method}`);
    console.log(`Response Payload: ${JSON.stringify(response, null, 2)}\n`);
}

async function main() {
    const config: MeshSDKConfig = {
        apiKey: API_KEY,
        chain: sepolia
    };

    try {
        const mesh = await initializeMeshSDK(config);

        let response: any = await mesh.enforcer.requestNonce({ account_address: mesh.getSigner.account.address });
        await logResponse('requestNonce', response);
        const nonce = response.nonce;

        const siweMessageParams = {
            domain: 'localhost',
            address: mesh.getSigner.account.address,
            uri: 'http://0.0.0.0',
            version: '1' as '1',
            chainId: 1,
            nonce: nonce,
            issuedAt: new Date().toISOString(),
        };

        const { message, signature } = await mesh.signSiWeMessage(siweMessageParams);
     
        response = await mesh.enforcer.authenticateSiWe({ message, signature });
        await logResponse('authorizeSiWe', response.token);

        // Invalidate the challenge so that the same challenge cannot be used again
        response = await mesh.enforcer.invalidateNonce({ account_address: mesh.signer.account.address });
        await logResponse('invalidateNonce', response);
        
    } catch (error) {
        console.error("An error occurred:", error);
    }
}

main().catch((error) => {
    console.error("Unhandled error in main function:", error);
    process.exit(1);
});

📘

Important Notes

  • API key is not required once a SiWE session is established
  • Nonces should be invalidated after use to prevent replay attacks
  • Authentication tokens should be stored securely
  • Implement proper error handling around each authentication step