Skip to main content

Overview

Sign In With Ethereum (SIWE, EIP-4361) lets users authenticate using an Ethereum wallet they already own. Instead of a password or OTP, the user signs a structured message with their private key, proving ownership of an address. This approach is ideal when:
  • Your users already have Ethereum wallets (Base App, Base Account, MetaMask, Phantom, hardware wallets, etc.)
  • You want a crypto-native sign-in experience without email or phone number requirements
  • You need to tie the user wallet identity to an on-chain address
On first sign-in, CDP can automatically create an embedded user wallet and associate it with the user’s Ethereum address. Control this with the ethereum.createOnLogin config option ("eoa" or "smart"). To disable automatic creation and provision wallets manually, omit createOnLogin from your config.

How it works

SIWE authentication is a two-step flow:
Your app calls signInWithSiwe with the user’s Ethereum address and context about your application. CDP returns a standards-compliant EIP-4361 message containing a cryptographic nonce, expiration time, and the parameters you provided. Present this message to the user’s wallet for signing.
After the user signs the message with their wallet, call verifySiweSignature with the flowId from step 1 and the resulting signature. CDP verifies the signature on-chain, and on success, returns an authenticated user with a user wallet.
  • Cryptographic proof: Authentication requires a valid signature from the private key controlling the address — no credential sharing
  • Replay protection: Each challenge contains a unique nonce and an expiration time
  • Domain binding: The domain field ties the signed message to your application, preventing cross-site replay attacks
  • Rate limiting: Protection against brute force attempts

Choosing an integration path

PathBest forPackages
Sign in with BaseBase ecosystem apps that want a one-line “Continue with Base” button@coinbase/cdp-react, @base-org/account
React hooks (custom wallet)Apps that already have a wallet connector (wagmi, viem, etc.)@coinbase/cdp-hooks
Non-ReactVanilla JS/TS or non-React frameworks@coinbase/cdp-core
Try SIWE live at demo.cdp.coinbase.com. Enable Sign in with Base in the demo’s config panel to see siwe:base in action alongside email, SMS, and OAuth methods.

Sign in with Base

Sign in with Base is a first-party SIWE auth method for CDP React apps. Add "siwe:base" to your authMethods config and CDP renders a Continue with Base button that handles wallet connection, message signing, and verification automatically. Under the hood, CDP uses the Base Account SDK (@base-org/account) to connect the user’s Base Account, then runs the standard SIWE flow (signInWithSiwepersonal_signverifySiweSignature). The chainId is read from the connected wallet at sign-in time. Unlike OAuth providers, no CDP Portal configuration is required — enable the method in your app config and install the Base Account SDK.

Prerequisites

Install the CDP React packages and the Base Account SDK:
npm install @coinbase/cdp-react @coinbase/cdp-hooks @coinbase/cdp-core @base-org/account
@base-org/account is an optional peer dependency of @coinbase/cdp-react. If it is not installed, clicking Continue with Base will fail with an error asking you to install the package.

Enable in your config

Add "siwe:base" to the authMethods array in your CDPReactProvider config. Set appName — it is passed to the Base Account SDK during wallet connection.
import { CDPReactProvider, AuthButton, type Config } from "@coinbase/cdp-react";

const config: Config = {
  projectId: "your-project-id",
  appName: "My App",
  appLogoUrl: "https://picsum.photos/64",
  ethereum: {
    createOnLogin: "smart",
  },
  authMethods: ["email", "siwe:base"],
};

function App() {
  return (
    <CDPReactProvider config={config}>
      <AuthButton />
    </CDPReactProvider>
  );
}
The authMethods value uses the siwe:<provider> format. Today the supported provider is "base" (i.e. "siwe:base"). See the AuthMethod and SIWE_METHODS type references for details.

Combining with other auth methods

siwe:base works alongside email, SMS, and OAuth methods in the same authMethods array. Users pick their preferred method from the sign-in UI:
authMethods: ["email", "sms", "oauth:google", "oauth:x", "siwe:base"],
When multiple methods are enabled, SignInAuthMethodButtons renders a button for each. Filter methods on individual components with the authMethods prop if needed.

SDK integration

React hooks (custom wallet)

Use useSignInWithSiwe and useVerifySiweSignature from @coinbase/cdp-hooks when you manage wallet connection yourself (for example, with wagmi or viem). You are responsible for obtaining the user’s address, connecting their wallet, and signing the challenge message.
import { useSignInWithSiwe, useVerifySiweSignature } from '@coinbase/cdp-hooks';

function SignInWithEthereum({ walletClient }) {
  const { signInWithSiwe } = useSignInWithSiwe();
  const { verifySiweSignature } = useVerifySiweSignature();

  const handleSignIn = async () => {
    const address = walletClient.account.address;

    // Step 1: Request the challenge message
    const { message, flowId } = await signInWithSiwe({
      address,
      chainId: 8453, // Base mainnet — use the chain the user is signing on
      domain: window.location.hostname,
      uri: window.location.origin,
    });

    // Step 2: Ask the user's wallet to sign the message
    const signature = await walletClient.signMessage({ message });

    // Step 3: Verify the signature and complete sign-in
    const { user, isNewUser } = await verifySiweSignature({ flowId, signature });

    console.log(`Signed in as ${user.userId} (${isNewUser ? 'new' : 'returning'} user)`);
  };

  return <button onClick={handleSignIn}>Sign In With Ethereum</button>;
}
The chainId must match the network the user’s wallet is on. Common values: 8453 (Base mainnet), 84532 (Base Sepolia), 1 (Ethereum mainnet). When using Sign in with Base, CDP reads the chain ID from the connected Base wallet automatically.

Non-React

For vanilla JavaScript/TypeScript or other frameworks, import directly from @coinbase/cdp-core:
import { initialize, signInWithSiwe, verifySiweSignature } from '@coinbase/cdp-core';

await initialize({ projectId: 'your-project-id' });

async function signInWithEthereum(walletClient) {
  const address = walletClient.account.address;

  // Step 1: Request the challenge message
  const { message, flowId } = await signInWithSiwe({
    address,
    chainId: 8453,
    domain: window.location.hostname,
    uri: window.location.origin,
  });

  // Step 2: Sign the message with the user's wallet
  const signature = await walletClient.signMessage({ message });

  // Step 3: Verify the signature and complete sign-in
  const { user, isNewUser } = await verifySiweSignature({ flowId, signature });

  console.log(`Signed in as ${user.userId} (${isNewUser ? 'new' : 'returning'} user)`);
}

Auth method linking

Users who are already signed in can link a SIWE address to their account so they can sign in with their external wallet later. Use useLinkSiwe from @coinbase/cdp-hooks — it follows the same two-step challenge-and-verify flow as useSignInWithSiwe, but associates the address with the current session instead of creating a new one. When a signed-in user clicks Continue with Base in CDP React, the SDK automatically calls linkSiwe instead of signInWithSiwe. See Auth Method Linking for the full linking guide and LinkAuth component examples.

Parameters

signInWithSiwe options

ParameterTypeRequiredDescription
addressstringYesERC-55 checksummed Ethereum address of the user
chainIdnumberYesEIP-155 chain ID (e.g. 8453 for Base mainnet, 1 for Ethereum mainnet)
domainstringYesRFC 3986 authority of your app (e.g. "example.com")
uristringYesRFC 3986 URI of the resource being accessed (e.g. "https://example.com")
statementstringNoHuman-readable ASCII assertion shown to the user
resourcesstring[]NoAdditional URIs the user acknowledges
idempotencyKeystringNoSafe retry key

signInWithSiwe result

FieldTypeDescription
messagestringEIP-4361-formatted message for the user to sign
flowIdstringPass this to verifySiweSignature
noncestringCryptographic nonce embedded in the message
expirationTimestringISO 8601 timestamp after which the challenge expires

verifySiweSignature options

ParameterTypeRequiredDescription
flowIdstringYesThe flowId returned by signInWithSiwe
signaturestringYesERC-191 hex signature (0x-prefixed) from the user’s wallet
idempotencyKeystringNoSafe retry key

verifySiweSignature result

FieldTypeDescription
userUserThe authenticated user object
messagestringConfirmation message
isNewUserbooleantrue if this is the user’s first sign-in

Optional message fields

You can include a statement to display a human-readable action the user is approving, and resources to enumerate URIs the user acknowledges:
const { message, flowId } = await signInWithSiwe({
  address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
  chainId: 8453,
  domain: 'example.com',
  uri: 'https://example.com',
  statement: 'Sign in to Example App to access your wallet.',
  resources: ['https://example.com/terms', 'https://example.com/privacy'],
});
The resulting EIP-4361 message will include these fields, making the user’s intent explicit in the signature payload.

Troubleshooting

Install the Base Account SDK in your project:
npm install @base-org/account
This package is an optional peer dependency of @coinbase/cdp-react — it is only required when "siwe:base" is in your authMethods.
The signed message must be submitted to verifySiweSignature before the expirationTime returned in step 1. If the user takes too long to sign, or the page is left open, request a new challenge by calling signInWithSiwe again.
If the user dismisses the wallet signing prompt, the flow fails gracefully. Prompt them to try again — each attempt creates a fresh challenge with a new nonce.
The chainId passed to signInWithSiwe must match the network the user’s wallet is connected to. For custom wallet integrations, read the chain ID from the connected provider. For Sign in with Base, CDP handles this automatically.
Challenge expiration: The signed message must be submitted to verifySiweSignature before the expirationTime returned in step 1. Expired challenges will be rejected.