James Bachini

Sign In With Ethereum Tutorial

Sign In With Ethereum

To sign in with Ethereum using Metamask we will be using the ERC4361 specification that allows Ethereum accounts to authenticate with off-chain services using a standard message format.

This technology offer users a self-custodial alternative to centralized identity providers that generally use email:password credentials. In Web3 applications we can improve user experiences and establish a new way for users to control and manage their digital identities.

Sign In With Ethereum Demo

There’s a simple demo here showing how this works (please only use test wallets and don’t sign anything with accounts that contain funds): https://jamesbachini.com/misc/signInWithEthereum.html

The source code for this is explained below.

There are risk associated with signing transactions. Some of these are mitigated by using plaintext messages which shows clearly in Metamask what you are signing. Still be vary wary of signing transactions on untrusted sites especially when you aren’t completely sure what you are signing. Always use a dedicated testnet wallet when experimenting.

How Sign In With Ethereum Works

  1. The wallet presents a plaintext message to the user for signing. This message includes specific information such as the user’s Ethereum address, the domain requesting the signing, a chain identifier, a nonce, and an issued-at timestamp.
  2. The message is then signed using the ERC-191 signed data format and presented to the relying party.
  3. The relying party checks the signature’s validity and message content before granting access to the requested service.
  4. Additional fields, such as expiration-time and resources, may be incorporated into the sign-in process.
  5. The relying party may fetch data associated with the Ethereum address, such as account balances and asset ownership, from the Ethereum blockchain or other data sources.

Sign In With Ethereum Example

The example code below is designed to be used as a boilerplate for creating your own request.

${domain} wants you to sign in with your Ethereum account:
${address}

${statement}

URI: ${uri}
Version: ${version}
Chain ID: ${chainId}
Nonce: ${nonce}
Issued At: ${issuedAt}
Expiration Time: ${expirationTime}
Not Before: ${notBefore}
Request ID: ${requestId}
Resources:
- ${resources[0]}
- ${resources[1]}

We can then use this to create the demo page with ethers.js Note that optional fields have been left out. The full code is also available in the Solidity Snippets scripts section

<!DOCTYPE html>
<html lang="en">
<body>
<button onclick="connectAndSign()">Connect &amp; Sign</button>
<div id="output"></div>
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>
<script>
const provider = new ethers.providers.Web3Provider(window.ethereum,'any');

const connectAndSign = async () => {
await provider.send("eth_requestAccounts", []);
    const signer = provider.getSigner()
    
    const domain = 'JamesBachini.com';
    const userAddress = await signer.getAddress();
    const statement = 'I accept the terms and wish to sign in with Ethereum';
    const uri = 'https://jamesbachini.com/misc/signInWithEthereum.html';
    const version = 1;
    const chainId = 0;
    const nonce = Math.round(Math.random() * 9999999);
    const issuedAt = Math.floor(Date.now() / 1000);

    const message = `${domain} wants you to sign in with your Ethereum account:
${userAddress}

${statement}

URI: ${uri}
Version: ${version}
Chain ID: ${chainId}
Nonce: ${nonce}
Issued At: ${issuedAt}`;

    let flatSignature = await signer.signMessage(message);
    console.log('flatSignature',flatSignature);
    let soliditySignature = ethers.utils.splitSignature(flatSignature);
    console.log('soliditySignature',soliditySignature);
    document.getElementById('output').innerHTML = flatSignature;
    // send signature for processing
}

</script>
</body>
</html>

This will request a signature like the screenshot below:

Sign in with Ethereum Example

We get two variables a flat signature and an object which can be used for verifications on-chain.

flatSignature = '0x0eb104dcf6a788675887432b2a6ab0b66595af5e251b1fd5f6358a9c96290b41641d7a6e2d3862351baea62cbe775440fd00062f655fc8fc9bd4966bfbfe26151c';
soliditySignature = {r: '0x0eb104dcf6a788675887432b2a6ab0b66595af5e251b1fd5f6358a9c96290b41', s: '0x641d7a6e2d3862351baea62cbe775440fd00062f655fc8fc9bd4966bfbfe2615', _vs: '0xe41d7a6e2d3862351baea62cbe775440fd00062f655fc8fc9bd4966bfbfe2615', recoveryParam: 1, v: 28};

Verifying Signatures

We want to verify signatures using either a smart contract or backend server. A centralized server obviously creates a single point of failure but the signature can be transferred using a REST API which then verifies it on the backend.

I haven’t tried this myself but there’s some example code here from Alchemy: https://docs.alchemy.com/docs/how-to-verify-a-message-signature-on-ethereum

Ideally we would be using a smart contract instead of a server which would check the address of the signer and then provide some sort of authentication for that user.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

contract Verify {
    function DontTrustVerify(bytes32 _hashedMessage, uint8 _v, bytes32 _r, bytes32 _s) public pure returns (address) {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 prefixedHashMessage = keccak256(abi.encodePacked(prefix, _hashedMessage));
        address signer = ecrecover(prefixedHashMessage, _v, _r, _s);
        return signer;
    }
}

There is a more detailed example in the ethers.js cookbook here: https://docs.ethers.org/v4/cookbook-signing.html

Conclusion

Sign In with Ethereum offers some advantages and disadvantages in terms of self-custody of credentials

Pros

  • Sign In with Ethereum allows users to have control over their digital identities and credentials, without relying on centralized identity providers.
  • The use of cryptographic keys for signing messages provides a high level of security, proven through real-world tests with billions of dollars at stake.
  • By standardizing the sign-in workflow, Sign-In with Ethereum can improve interoperability across off-chain services for Ethereum-based authentication.
  • Sign-In with Ethereum can provide wallet vendors a consistent machine-readable message format to achieve improved user experiences and consent management.

Cons

  • Sign-In with Ethereum requires technical knowledge of how to sign messages with cryptographic keys, which may be challenging for some developers.
  • Sign-In with Ethereum is a relatively new specification and may not be widely adopted by off-chain services, limiting its usefulness to web3 specific applications.
  • Since self-custody means the user is responsible for their own credentials, there is a risk of losing access to their wallet or mismanaging their digital identity. In this scenario there is no recovery option

I hope you have found this tutorial useful and it has provided an interesting example of how to use sign in with Ethereum in the wild.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.