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
Section titled “1. Sign — client side”import { SiwsMessage } from "@talismn/siws"
// get the nonce from your backendconst { 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.jsconst injected = await window.injectedWeb3["talisman"].enable("My dApp")const [account] = await injected.accounts.get()
// construct the sign-in messageconst 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 userconst message = siwsMessage.prepareMessage()const { signature } = await injected.signer.signRaw({ address: account.address, data: message, type: "payload",})
// send { message, signature, address } to your backendawait fetch("/api/verify", { method: "POST", body: JSON.stringify({ message, signature, address: account.address }),})2. Verify — backend
Section titled “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 addressconst siwsMessage = await verifySIWS(message, signature, address)
// prevent replay attacks: the nonce in the signed message must match// the one you issued for this sessionif (siwsMessage.nonce !== expectedNonce) throw new Error("Invalid nonce!")
// prevent phishing: the user must have signed a message for YOUR siteif (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
Section titled “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.