← Developers Coinductor Auth · Integration guide

Sign in with Coinductor.

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.

  1. Create a challenge

    Your backend issues a random, single-use nonce. The reference lifetime is five minutes.

  2. 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.

  3. Ask the customer to approve

    The customer scans the QR in Coinductor, reviews the requesting website and confirms access in the wallet.

  4. 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.

  5. 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:

PORT=3000
ALLOWED_ORIGINS=https://auth.example.com
NONCE_TTL_MS=300000

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:

{
  "action": "getSession",
  "data": {
    "sessionId": "<relay-session-id>",
    "origin": "https://shop.example.com"
  }
}

The mobile side sends the encrypted envelope to that session:

{
  "action": "sendTo",
  "to": "<relay-session-id>",
  "domainName": "shop.example.com",
  "message": "<JSON-encoded encrypted envelope>"
}

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.

sharedKey = nacl.box.before(mobilePublicKey, browserSecretKey)
finalKey = SHA3_256(SHA3_256(sharedKey) || SHA3_256(emptyBytes))
plaintext = nacl.secretbox.open(ciphertext, iv, finalKey)

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.

{
  "nonce": "<single-use-uuid>",
  "expiresIn": 300,
  "expiresAt": 0
}

expiresAt is a Unix timestamp in milliseconds; the zero above is a placeholder. Use the actual server response.

POST /api/auth/verify

Send the decrypted fields as JSON to your backend:

{
  "publicKey": "<compressed-public-key-hex>",
  "signature": "<signature-hex>",
  "nonce": "<issued-challenge>"
}

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

  1. Run the example and open its QR page.
  2. Expand the protocol log and copy the assembled QR string from your current session.
  3. Run the command below in a second terminal inside the example folder.
  4. 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

  1. Confirm the app version with the integration team.
  2. Open the reference QR page on your computer.
  3. Scan it using Coinductor’s QR scanner.
  4. Review the requesting website and approve sign-in.
  5. 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.