Why wallet signatures instead of passwords?

  1. No new passwords – authentication happens with the key the user already controls.
  2. Nothing to steal or reuse – each login is a one-off, domain-bound signature that never leaves the user’s device.
  3. Wallet-agnostic – works in any EIP-1193 wallet (browser extension, mobile deep-link, embedded provider) and follows the open “Sign in with Ethereum” (SIWE) EIP-4361 standard.

Base Accounts build on those standards so you can reuse any SIWE tooling – while still benefiting from passkeys, session keys, and smart-wallet security.

High-level flow

Undeployed Smart Wallets?
Base Account signatures include the ERC-6492 wrapper so they can be verified even before the wallet contract is deployed. Viem’s verifyMessage and verifyTypedData handle this automatically.

Implementation

Code Snippets

import { createBaseAccountSDK } from "@base-org/account";

// Initialise the SDK (no config needed for defaults)
const provider = createBaseAccountSDK().getProvider();

// 1 — fetch a nonce from your backend
const nonce = await fetch('/auth/nonce').then(r => r.text());

// 2 — connect and authenticate
try {
  const { accounts } = await provider.request({
    method: 'wallet_connect',
    params: [{
      version: '1',
      capabilities: {
        signInWithEthereum: { nonce, chainId: '0x1' }
      }
    }]
  });
  const { address } = accounts[0];
  const { message, signature } = accounts[0].capabilities.signInWithEthereum;
  await fetch('/auth/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ address, message, signature })
  });
} catch (err) {
  console.error(`err ${err}`);
}

If using the above code beyond Base Account, note that not every wallet supports the new wallet_connect method yet. If the call throws method_not_supported, fall back to using eth_requestAccounts and personal_sign.

For a full React example see the React + Wagmi guide.

Example Express Server

server/auth.ts
import crypto from 'crypto';
import express from 'express';
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

const app = express();
app.use(express.json());

// Simple in-memory nonce store (swap for Redis or DB in production)
const nonces = new Set<string>();

app.get('/auth/nonce', (_, res) => {
  const nonce = crypto.randomBytes(16).toString('hex');
  nonces.add(nonce);
  res.send(nonce);
});

const client = createPublicClient({ chain: base, transport: http() });

app.post('/auth/verify', async (req, res) => {
  const { address, message, signature } = req.body;

  // 1. Check nonce hasn\'t been reused
  const nonce = message.match(/at (\w{32})$/)?.[1];
  if (!nonce || !nonces.delete(nonce)) {
    return res.status(400).json({ error: 'Invalid or reused nonce' });
  }

  // 2. Verify signature
  const valid = await client.verifyMessage({ address, message, signature });
  if (!valid) return res.status(401).json({ error: 'Invalid signature' });

  // 3. Create session / JWT here
  res.json({ ok: true });
});

app.listen(3001, () => console.log('Auth server listening on :3001'));

Add the Base Sign In With Base Button

Use the pre-built component for a native look-and-feel:

Checkout.tsx
import { SignInWithBaseButton } from '@base-org/account-ui/react';

export function Checkout() {
  return (
    <SignInWithBaseButton
      colorScheme="light"
      onClick={() => signInWithBase()}
    />
  );
}

See full props and theming options in the Button Reference and Brand Guidelines.