Skip to content

Quickstart

A SIWS sign-in is two steps: the user signs a structured message with their wallet, and your backend verifies the signature.

The message includes a nonce — just a random single-use token (e.g. crypto.randomUUID()) generated by your backend, so a captured signature can’t be replayed. Have an endpoint return one, keep track of it the way you keep any other session state (session cookie, cache, database — whatever your stack provides), and check it matches at verification time.

1. Sign — client side

import { SiwsMessage } from "@talismn/siws"

// get the nonce from your backend
const { nonce } = await fetch("/api/nonce").then(res => res.json())

// connect to the wallet extension — or use your favourite Substrate library,
// such as polkadot-api or polkadot.js
const injected = await window.injectedWeb3["talisman"].enable("My dApp")
const [account] = await injected.accounts.get()

// construct the sign-in message
const siwsMessage = new SiwsMessage({
  domain: "myapp.com", // your site — the backend rejects other domains
  uri: "https://myapp.com/signin",
  address: account.address,
  nonce,
  statement: "Welcome! Sign in to continue.",
  chainName: "Polkadot",
})

// prepare the exact string to sign, and ask the wallet for a signature —
// a human-readable message is shown to the user
const message = siwsMessage.prepareMessage()
const { signature } = await injected.signer.signRaw({
  address: account.address,
  data: message,
  type: "payload",
})

// send { message, signature, address } to your backend
await fetch("/api/verify", {
  method: "POST",
  body: JSON.stringify({ message, signature, address: account.address }),
})

2. Verify — backend

// e.g. POST /api/verify — receives { message, signature, address }
import { verifySIWS } from "@talismn/siws"

// throws if the signature doesn't match the message and address
const siwsMessage = await verifySIWS(message, signature, address)

// prevent replay attacks: the nonce in the signed message must match
// the one you issued for this session
if (siwsMessage.nonce !== expectedNonce) throw new Error("Invalid nonce!")

// prevent phishing: the user must have signed a message for YOUR site
if (siwsMessage.domain !== "myapp.com") throw new Error("Wrong domain!")

// done — the user has proven they own `address`.
// issue a session or JWT as you would with any other login method.

verifySIWS runs in any JavaScript runtime — Node.js, edge runtimes like Cloudflare Workers, or even the browser. And it doesn’t care how the message was signed: any Substrate signing interface works — see SiwsMessage for polkadot-api and dedot signing recipes.

Complete example

For a full working integration — React UI, wallet selection, JWT sessions, and a protected API, deployable to Cloudflare Workers — see the demo app source or try it live at siws.xyz.