Let customers approve website access with their wallet. Build a QR
sign-in flow, verify the response on your backend and connect the
verified wallet key to your own account system.
Protocol reference 2.3.0Reference example · partner accessProduction review required
This guide describes the current reference implementation. Confirm
supported mobile app versions and the approved signing format with
Coinductor before launch. Authentication establishes control of a
signing key; it does not verify legal identity or authorize a payment.
One approval, your account rules
From scan to session.
Use the verified wallet key as an account identifier, offer
wallet-based registration, or link a wallet to an existing account.
Your service decides which of these actions is allowed.
Create a challenge
Your backend issues a random, single-use nonce. The reference
lifetime is five minutes.
Display a QR code
The browser generates an ephemeral encryption key pair, obtains a
relay session ID and combines these with the nonce and website
hostname.
Ask the customer to approve
The customer scans the QR in Coinductor, reviews the requesting
website and confirms access in the wallet.
Receive and verify
The wallet sends an encrypted response through the relay. The
browser decrypts it and sends the public key, signature and nonce to
your backend for verification.
Establish your session
After verification, your backend applies account rules and creates a
real authenticated session. Account linking should also require
authorization from the existing account.
This is an authentication flow. It does not request a token transfer or
demonstrate Connected Checkout.
Reference application
Run your first example.
The example includes an Express backend, a browser QR interface,
bundled browser libraries and a command-line wallet simulator.
Before you start
Request access to the Coinductor-Auth reference repository.
Use a maintained Node.js release compatible with the example’s
declared minimum of Node.js 18.
Use a Coinductor mobile build confirmed to support this protocol,
or start with the simulator.
Allow browser and simulator access to
wss://wss.coinductor.io.
Install and run
After cloning the repository supplied with your access, run these
commands inside its folder:
npm ci
npm start
Open http://localhost:3000 on your computer. A QR code
appears after the browser connects to the relay and obtains a
challenge.
Configuration and file map
Copy the supplied environment example to your local configuration.
For a staging domain, set the allowed origin to that exact HTTPS
origin:
auth.example.com is an example, not a Coinductor
service. The current demo also allows local development origins;
configuring this value alone does not harden it for production.
server.js
Challenge issuance and signature verification on your server.
public/app.js
Browser keys, relay events, QR rendering, decryption and
verification requests.
public/index.html and public/style.css
The reference interface and its presentation.
public/vendor/
Bundled browser cryptography and QR libraries.
test-simulate-mobile.js
A simulator that generates a test signing key and sends an
encrypted response.
Protocol reference
What the devices exchange.
The hosted relay transports messages between the browser session and
mobile wallet. Your own backend remains responsible for authenticating
the response.
QR payload and challenge lifetime
{sessionId}:{webPublicKey}:{nonce}:{domainName}
sessionId
UUID session identifier returned by the relay for the browser
connection.
webPublicKey
The browser’s ephemeral 32-byte Curve25519 public key, encoded as
Base64. Its secret key stays in browser memory.
nonce
A single-use UUID challenge issued by your backend, with a default
expiry of 300 seconds.
domainName
Your website hostname, such as shop.example.com. The
reference maps localhost and local IP development hosts to
localhost.
Show the expiry to the customer and obtain a new challenge when it
expires. The backend’s expiry check is authoritative. In a
production implementation, bind each challenge to its intended login
attempt and invalidate replaced challenges.
Relay connection and events
Connect the browser to wss://wss.coinductor.io, then
request a session. The reference sends a keep-alive ping every 25
seconds while connected.
{ "action": "getSession" }
{ "action": "ping" }
The session response provides data.sessionId and the
connection origin:
The browser receives action: "clientMessage" and reads
the envelope from data.message. Connection loss must
return the UI to a retry state. Confirm relay error envelopes with
the supported release; the reference client handles
sendToError, while the README also shows an error
attached to sendTo.
Encrypted response and signing format
The documented JSON envelope contains three Base64 fields:
ephemeralPublicKey
The mobile side’s ephemeral 32-byte Curve25519 public key.
iv
A 24-byte cryptographic nonce, distinct from the backend’s login
challenge.
encryptedData
XSalsa20-Poly1305 SecretBox ciphertext including its
authentication tag.
The reference uses nacl.box.before to obtain a
precomputed key, derives the SecretBox key with SHA3-256, and
decrypts the envelope. Use the supplied implementation and
compatible library versions; raw X25519 output is not a drop-in
replacement for nacl.box.before.
This is an algorithm outline: || means byte-array
concatenation. Reject decryption failure. The decrypted object
supplies publicKey, signature and
nonce; optional address fields are not independently
verified by the example backend.
The simulator signs the UTF-8 message below, hashed with SHA3-256,
using secp256k1 with low-S signatures:
{domainName}:{publicKey}:{nonce}
The public key is compressed hex (33 bytes / 66 characters). The
simulator emits a compact signature, while the README includes a
DER-shaped example. Confirm encoding and one canonical domain-bound
signing format with the mobile team. The demo accepts additional
legacy formats, including one without a domain; do not carry that
fallback into a production verifier.
Your server
Verify before granting access.
These routes belong to the reference application running on your
backend. They are not public Coinductor REST endpoints.
GET /api/nonce
Returns a new challenge and its expiry. The sample stores challenges
in memory.
The reference checks the allowed origin, consumes the challenge,
checks expiry and verifies the signature. A successful response
contains success, token and
user.publicKey. Its random token is a demonstration
value; implement session storage or signed tokens, expiry and logout
before using it for account access.
Verification errors and recovery
400 BAD_REQUEST
Required fields are missing. Correct the request rather than
granting access.
400 INVALID_PUBLIC_KEY
The key does not have the required compressed hex shape.
403 FORBIDDEN_ORIGIN
The origin is not allowed. Check the configured website origin.
422 NONCE_EXPIRED_OR_USED
The challenge is absent, consumed or already removed. Start a
fresh attempt.
422 NONCE_EXPIRED
The challenge has passed its expiry. Generate a new QR code.
401 INVALID_SIGNATURE
The signature did not verify. Do not create a session; use a new
challenge for a retry.
The reference consumes a recognized challenge before signature
verification. A failed signature therefore requires a fresh
challenge too.
Test the complete flow
Start with a test identity.
With the simulator
Run the example and open its QR page.
Expand the protocol log and copy the assembled QR string from your
current session.
Run the command below in a second terminal inside the example
folder.
Check the browser’s verified response. A simulator delivery
message alone is not proof of successful authentication.
node test-simulate-mobile.js "<COPIED_QR_STRING>"
The simulator generates a test key. It does not require your
recovery phrase and does not send a blockchain transaction. It uses
the hosted relay, so network access is required.
With Coinductor on a phone
Confirm the app version with the integration team.
Open the reference QR page on your computer.
Scan it using Coinductor’s QR scanner.
Review the requesting website and approve sign-in.
Confirm that your backend verified the response and that the
correct account flow follows.
Also test rejection, expiry, reconnecting, repeated submissions and
wrong-domain requests. Test account linking and logout in your own
application; the demo’s logout only reloads its page.
Before launch
Complete the production integration.
Review these items with your team and Coinductor before enabling real
customer access.
Agree the protocol version. Confirm supported mobile
builds, signature encoding, domain normalization and relay error
shapes. Use one approved domain-bound message format.
Restrict origins. Require HTTPS and explicit
production origins. Remove automatic development-origin allowances and
missing-origin fallbacks. An Origin header alone is not proof of
identity.
Bind and consume challenges. Associate each nonce
with its browser login attempt, expiry and intended origin. Use shared
storage with atomic consumption if running multiple backend instances.
Implement real sessions. Apply account
creation/linking rules and issue secure sessions with expiry, rotation
and server-enforced logout.
Limit and validate requests. Add rate limits, strict
payload validation and controlled error responses. Remove the demo’s
raw decrypted-payload and session-token logging.
Test failure paths. Verify replay rejection,
wrong-domain signatures, expired/replaced QR codes, corrupted
messages, parallel attempts and disconnects on supported browsers and
devices.
The reference demonstrates protocol mechanics. Its compatibility
fallbacks, in-memory nonce store and demonstration session token need
production review. Keep payment requests and blockchain confirmation
handling separate from authentication.
Build with Coinductor
Get the reference example.
Request developer access with your website domain, intended sign-in flow
and technical contact. The team can share the private reference
repository and confirm the supported app builds.