GitHub - qrauth-io/qrauth: Identity verification platform — passwordless login, QR fraud protection, and physical access control. QR codes + passkeys + device trust under one SDK.

38 min read Original article ↗

QRAuth — Identity verification infrastructure

Cryptographic authentication infrastructure with QR codes, passkeys, and device trust.

Website · Docs · API Reference · Issues


QRAuth is an identity verification platform that unifies QR-based authentication and WebAuthn passkeys under a single identity model. Same-device? Passkeys. Cross-device? QR scan. Both under one SDK, one identity, zero app installs — users scan with their phone camera and verify in the browser. No proprietary app required.

It includes animated cryptographic QR codes (Living Codes) with dual-engine rendering (Canvas 2D + CanvasKit/Skia WASM) that rotate frames every second with HMAC-signed payloads — making screenshots and replay attacks impossible — a Trust Reveal UX with full-screen verification overlays and crystalline visual fingerprints, a device trust registry (NEW/TRUSTED/SUSPICIOUS/REVOKED) with user dashboard, ephemeral delegated access for account-free temporary sessions, a Proximity Verification API that issues signed JWT attestations proving physical presence, drop-in Web Components (<qrauth-login>, <qrauth-2fa>, <qrauth-ephemeral>), a 6-signal real-time fraud detection engine with adaptive per-organization scoring, ECDSA-P256 challenge-response verification, geospatial binding, a multi-tenant admin control plane with KYC workflows and audit trails, and developer SDKs for Node.js and Python.

Three Use Cases

  1. Identity Verification — Passwordless login via QR scan or passkey. Like WhatsApp Web: scan a QR code on your phone to authenticate a desktop session, or tap a passkey for instant biometric sign-in. No password, no app install.
  2. QR Fraud Protection — Cryptographic verification that a physical or digital QR code is legitimate, untampered, and issued by the claimed organization. ECDSA-P256 signatures, geospatial binding, and transparency logs.
  3. Physical Access Control — Scan to unlock physical spaces, devices, and assets. Device trust levels control which devices can authenticate, with automatic anomaly detection.

Think of it as Auth0 for physical and cross-device authentication. Auth0 became the authentication layer for web identities. QRAuth adds the physical layer — QR codes, device trust, and passkeys.

QRAuth Dashboard — Create signed QR codes
Dashboard: create cryptographically signed QR codes with content types, templates, and live preview


Table of Contents


How QRAuth Compares

Capability QRAuth Passkeys Only Auth0 / Clerk TOTP / 2FA Magic Links
Passwordless Yes Yes Optional No Yes
Cross-device auth (desktop via phone) Yes (QR) Limited No No No
WebAuthn/FIDO2 passkeys Yes (built-in) Yes Plugin No No
Device trust registry Yes (NEW/TRUSTED/SUSPICIOUS) No No No No
Fraud detection engine 6-signal + adaptive AI No Basic No No
Geospatial binding Yes (PostGIS) No No No No
Physical QR verification Yes No No No No
Proximity attestation (signed JWT) Yes No No No No
Transparency log Yes (append-only) No No No No
Challenge-response protocol Yes (ECDSA-P256) Yes (WebAuthn) OAuth2 HMAC Token
Works without app install Yes (web-based) Yes Yes No (app needed) Yes
Works on kiosks/smart TVs Yes No No No No

Key distinction: QRAuth and passkeys are complementary, not competitive. QRAuth natively supports passkeys — use passkeys for platform-native flows and QR auth for cross-device, kiosk, and physical scenarios. Both are unified under one identity, one dashboard, one SDK.


Problem

QR code fraud is a global epidemic. Scammers print fake QR code stickers and paste them over legitimate ones on parking meters, government signs, and payment terminals. Victims scan the code, land on a convincing phishing site, and enter payment credentials. There is currently no built-in mechanism to verify that a physical QR code was placed by the legitimate authority.

Real-world incidents (2025-2026):

  • Austin, TX: 29 compromised parking pay stations discovered
  • NYC DOT: Urgent public warning about fraudulent QR codes on parking meters
  • Southend-on-Sea, UK: ~100 fake QR stickers removed from parking signage
  • Bucharest, Romania: City Hall warning about fake QR codes on parking meters (March 2026)
  • Thessaloniki, Greece: Arrest of individual replacing metal parking signs with plastic ones containing fraudulent QR codes (March 2026)
  • INTERPOL Operation Red Card 2.0: 651 suspects arrested across 16 nations (February 2026)

The fraud detection market for ticketing alone is valued at USD 1.87 billion (2024), growing at 16.2% CAGR to USD 5.47 billion by 2033. The broader QR code payment and verification market is orders of magnitude larger.


Solution

QRAuth is infrastructure, not an app. It provides the invisible verification layer that any QR-generating application can embed via SDK or API.

The Platform Model

┌──────────────────────────────────────────────────────────────────┐
│                     Applications (Your Products)                 │
│                                                                  │
│  Parking App    Payment App    Event Platform    Restaurant POS  │
│      │              │               │                │           │
│      └──────────────┴───────────────┴────────────────┘           │
│                             │                                    │
│                    @qrauth/sdk integration                       │
│                             │                                    │
├─────────────────────────────┼────────────────────────────────────┤
│                      QRAuth Platform                             │
│                                                                  │
│   Signing Engine ─── Verification Edge ─── Geo Registry          │
│   Fraud Detection ── WebAuthn Service ─── Event/Webhook Bus      │
│   Transparency Log ─ Tenant Management ── Analytics Pipeline     │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

How It Works

  1. Developer integrates SDK: npm install @qrauth/node — generates signed QR codes and verifies them via API.
  2. QR code is deployed: The QR encodes a short verification URL (https://qrauth.io/v/[token]) or the app verifies server-side via SDK.
  3. User scans: Any phone camera opens the verification URL. No app install required. The page shows issuer identity, location match, and an ephemeral visual proof.
  4. Trust escalates: Returning users create WebAuthn passkeys for cryptographic, unphishable verification.
  5. Events fire: Every scan, verification, and fraud detection triggers webhooks to the integrating application.

Key Differentiators

  • Identity platform, not a QR tool: QR codes and passkeys are authentication methods — the platform handles identity, device trust, and fraud.
  • Device trust registry: Every login detects and registers the device. Users manage trusted devices with trust levels (NEW/TRUSTED/SUSPICIOUS) and revocation.
  • WebAuthn passkeys: Native support for passkey registration and authentication. Passkeys and QR auth coexist under one identity.
  • 6-signal fraud detection: Duplicate location, proxy detection, geo-impossibility, scan velocity, bot detection, device clustering — with adaptive per-org scoring.
  • Developer-first: Node.js and Python SDKs. 10-line integration.
  • Challenge-response protocol: ECDSA-P256 signatures with content hashing — not static tokens.
  • Open protocol (QRVA): Published specification anyone can implement. QRAuth is the reference implementation.
  • No app install required: Web-based verification works on any phone camera.
  • Multi-tenant control plane: Dashboard with KYC workflows, audit trails, fraud incident management, session visibility, and analytics.
  • AI security agent: Daily automated analysis that creates fraud rules going live in <60 seconds.
  • Transparency log: Public, append-only audit trail of all issued QR codes.

Quick Start

Install the SDK

Generate a Signed QR Code

import { QRAuth } from '@qrauth/node';

const qrauth = new QRAuth({
  tenantId: 'parking-thessaloniki',
  apiKey: process.env.QRAUTH_API_KEY,
});

const qr = await qrauth.create({
  destination: 'https://parking.thessaloniki.gr/pay/zone-b',
  location: { lat: 40.6321, lng: 22.9414, radius: 15 },
  metadata: { zone: 'B', rate: '2.00/hr' },
  expiresIn: '1y',
});

console.log(qr.verificationUrl);
// https://qrauth.io/v/xK9m2pQ7

console.log(qr.qrImageUrl);
// https://api.qrauth.io/qr/xK9m2pQ7.png

Verify a Scanned QR Code (Server-Side)

const result = await qrauth.verify('xK9m2pQ7', {
  scannerLat: 40.6325,
  scannerLng: 22.9410,
});

console.log(result);
// {
//   verified: true,
//   issuer: { name: 'Municipality of Thessaloniki', trustLevel: 'government' },
//   locationMatch: { matched: true, distance: 12 },
//   trustScore: 94,
//   destination: 'https://parking.thessaloniki.gr/pay/zone-b'
// }

Embed the Verification Badge (Client-Side)

<script src="https://cdn.qrauth.io/badge.js"></script>
<div data-qrauth-badge="xK9m2pQ7"></div>

This renders an inline trust badge inside your application — the user never leaves your app.


SDKs

QRAuth provides first-class SDKs for major platforms. Each SDK handles signing, verification, key caching, and webhook consumption. Node.js and Python are stable today; additional languages are coming soon.

SDK Package Status
Node.js / TypeScript @qrauth/node v0.1.2 — Stable
Python qrauth (PyPI) v0.1.2 — Stable
Web Components @qrauth/web-components v0.1.0 — Stable
Animated QR @qrauth/animated-qr v0.1.0 — Stable
Go github.com/qrauth-io/qrauth-go Planned
PHP qrauth/qrauth-php (Composer) Planned
Swift (iOS) QRAuth (SPM) Planned
Kotlin (Android) io.qrauth:sdk (Maven) Planned
React Wrapper @qrauth/react Planned

SDK Design Principles

Every SDK follows the same contract:

qrauth.create(options)   → Generate a signed QR code
qrauth.verify(token)     → Verify a QR code (server-side)
qrauth.revoke(token)     → Revoke a QR code
qrauth.on(event, fn)     → Listen to webhook events
qrauth.tenant()          → Get tenant configuration

React Component

import { QRAuthBadge } from '@qrauth/react';

function PaymentPage({ qrToken }) {
  return (
    <div>
      <h2>Scan to Pay</h2>
      <QRAuthBadge
        token={qrToken}
        theme="dark"
        showLocation={true}
        onVerified={(result) => console.log('Verified:', result)}
        onFraudDetected={(alert) => notifyAdmin(alert)}
      />
    </div>
  );
}

Architecture

System Overview

                          ┌─────────────────────────┐
                          │     Developer Portal    │
                          │   docs.qrauth.io        │
                          │   API Explorer + Guides │
                          └────────────┬────────────┘
                                       │
                          ┌────────────▼─────────────┐
                          │      API Gateway         │
                          │      (Node/Fastify)      │
                          │      api.qrauth.io       │
                          └────────────┬─────────────┘
                                       │
          ┌────────────────────────────┼────────────────────────────┐
          │                            │                            │
┌─────────▼──────────┐   ┌─────────────▼────────────┐   ┌─────────────▼──────────┐
│  Tenant Manager    │   │  QR Signing Engine       │   │  Geo Registry          │
│  (Isolation, keys, │   │  (ECDSA-P256, KMS)       │   │  (PostGIS)             │
│   branding, config)│   │                          │   │                        │
└────────────────────┘   └──────────────────────────┘   └────────────────────────┘
                                       │
                          ┌────────────▼─────────────┐
                          │   Verification Edge      │
                          │   (Cloudflare Workers)   │
                          │   qrauth.io/v/[token]    │
                          └────────────┬─────────────┘
                                       │
          ┌────────────────────────────┼────────────────────────────┐
          │                            │                            │
┌─────────▼──────────┐   ┌─────────────▼────────────┐   ┌─────────────▼──────────┐
│  Fraud Detection   │   │  Event / Webhook Bus     │   │  Analytics Pipeline    │
│  (ML pipeline)     │   │  (Redis Streams)         │   │  (ClickHouse)          │
└────────────────────┘   └──────────────────────────┘   └────────────────────────┘
                                       │
                          ┌────────────▼─────────────┐
                          │   Transparency Log       │
                          │   (Append-only, public)  │
                          └──────────────────────────┘

Core Services

API Gateway (Node.js / Fastify) — Entry point for all SDK and portal operations. Handles authentication (JWT + API keys), per-tenant rate limiting, request validation, and routing. Exposes RESTful endpoints for tenant management, QR code lifecycle, verification, analytics, and webhook configuration.

Tenant Manager — Manages multi-tenant isolation. Each tenant receives: an isolated namespace, dedicated signing keys (KMS-backed), custom branding for verification pages, an independent user pool for WebAuthn passkeys, separate analytics partitions, and configurable webhook endpoints. Supports SSO/SAML for enterprise tenants.

QR Signing Engine — Core cryptographic service. Generates ECDSA-P256 key pairs per tenant, signs QR payloads, and manages key rotation. Private keys are stored in AWS KMS or HashiCorp Vault — never in application memory.

signature = ECDSA_Sign(tenant_private_key, SHA256(token + destination_url + geo_hash + expiry))

Geo Registry (PostGIS) — Stores the physical location of every registered QR code. Each registration includes GPS coordinates, accuracy radius, and location metadata. Supports spatial queries for verification and fraud detection.

Verification Edge (Cloudflare Workers) — The public-facing verification endpoint deployed globally on edge nodes for sub-50ms cryptographic verification. Handles token lookup, signature verification, geospatial matching, and anti-proxy detection at the edge. Visual proof generation runs on the Fly.io API server (requires native image processing unavailable in V8 isolates).

Event / Webhook Bus (Redis Streams) — Every platform event (scan, verification, fraud detection, QR creation, revocation) is published to a stream. Tenants configure webhook endpoints to receive events in real-time. Supports retry with exponential backoff, dead-letter queues, and event replay.

Fraud Detection Pipeline — ML pipeline that analyzes scan patterns in real-time. Detects anomalies: new QR codes appearing at locations with existing registrations, sudden changes in scan patterns, scans from known proxy IP ranges, and geographic impossibilities. Per-tenant tuning allows customers to adjust sensitivity thresholds.

Analytics Pipeline (ClickHouse) — Columnar time-series database for scan event storage and analysis. Powers tenant dashboards (scan counts, peak times, geographic heatmaps) and feeds the fraud detection model. Handles millions of scan events per day. Data is partitioned per tenant for isolation.

Transparency Log — Public, append-only ledger of all QR code issuances. Each entry contains: tenant ID, token, destination URL hash, registration timestamp, and geolocation hash. Third parties can audit the log to verify QR code legitimacy. Inspired by Certificate Transparency (RFC 6962).


Security Model

QRAuth implements a four-tier progressive security model. Each tier builds on the previous, and users automatically escalate through tiers over time. All tiers are active simultaneously — there is no manual configuration required.

Tier 1 — Cryptographic Signing (Baseline)

Every QR code generated through QRAuth includes a digital signature created using ECDSA-P256 with the tenant's private key (managed in KMS).

Verification flow:

1. User scans QR → browser opens https://qrauth.io/v/[token]
2. Edge worker looks up token → retrieves: destination_url, tenant_id, signature, geo_data
3. Edge worker fetches tenant's public key (cached at edge)
4. Edge worker verifies: ECDSA_Verify(public_key, signature, payload_hash)
5. If valid → display verified issuer identity and destination URL
6. If invalid → display fraud warning + trigger alert event

Defeats: QR codes pointing to arbitrary URLs that were never registered on the platform.

Tier 2 — Ephemeral Server-Generated Visual Proof (Anti-Cloning)

The verification page renders a server-generated image (PNG, computed per-request) containing personalized, time-bound information that a cloned page cannot reproduce:

  • User's approximate location (derived from IP geolocation): "Thessaloniki, Central Macedonia"
  • User's device type (from User-Agent): "iPhone 15, Safari"
  • Exact timestamp (to the second): "14:32:07 EEST"
  • Procedural visual fingerprint: A unique abstract pattern generated from HMAC(server_secret, token + timestamp + client_ip_hash)

The image is rendered server-side using Sharp/Canvas. It is a rasterized image (not HTML/CSS), making pixel-perfect reproduction computationally expensive.

Defeats: Static page cloning. A cloned page shows stale timestamps, wrong locations, and missing visual fingerprints.

Tier 3 — Anti-Proxy Detection (Anti-MitM)

Defends against real-time reverse proxying where a scammer forwards requests to the real QRAuth server and relays responses to the victim.

Signal Method Confidence Availability
TLS Fingerprint (JA3/JA4) Compare TLS handshake against User-Agent claim High Requires Cloudflare Bot Management (Enterprise)
Latency Analysis Measure RTT to edge; proxied requests add 50-200ms Medium All tiers
Canvas Fingerprint Hash of canvas render output; differs between devices High All tiers
JS Environment Integrity window.location.origin check, performance.getEntries() validation Medium All tiers
IP/Geo Consistency IP geolocation vs. browser Geolocation API comparison Medium All tiers
Header Analysis Detect proxy-injected headers (X-Forwarded-For, Via) Low-Medium All tiers

Each signal contributes to a composite trust score (0-100). Scores below threshold trigger a warning banner and a fraud.proxy_detected webhook event.

Defeats: Real-time page proxying. The proxy's TLS fingerprint, latency profile, and canvas output differ from a genuine browser.

Tier 4 — WebAuthn Passkeys (Unphishable)

WebAuthn credentials are origin-bound at the OS/hardware level. A passkey created for https://qrauth.io will ONLY activate on https://qrauth.io. A phishing page on any other domain physically cannot trigger the passkey prompt. This is enforced by the authenticator (Secure Enclave, Titan chip, TPM), not by JavaScript.

Enrollment flow:

1. User verifies a QR code via Tier 1-3 (first visit)
2. Verification page offers: "Create a passkey for instant verification"
3. User taps → biometric prompt (Face ID / fingerprint)
4. Browser generates key pair, private key stored in Secure Enclave
5. Public key sent to QRAuth server, associated with tenant's user pool
6. Future scans trigger passkey automatically

Defeats: Everything — including sophisticated real-time proxying, social engineering, and domain spoofing.

Tier Summary

Tier Defeats Requires User Friction
1 - Signed QR Unregistered fake QRs Nothing (automatic) None
2 - Visual Proof Static page cloning Nothing (automatic) None
3 - Anti-Proxy Real-time MitM proxying Nothing (automatic) None
4 - WebAuthn All known phishing vectors One-time passkey creation Biometric tap

Animated QR — Living Codes

Living Codes are animated, cryptographically signed QR codes that rotate frames at a configurable interval (default: 1 second). Each frame encodes a unique HMAC-SHA256 signature, making screenshot-and-forward attacks, screen sharing relays, and replay attacks impossible.

Living Codes — animated cryptographic QR code demo
Living Code rotating frames every 500ms with HMAC-SHA256 signed payloads

Live demo: qrauth.io/demo/qranimation

How It Works

  1. Server creates a display session and issues a per-session frameSecret (HMAC key derived from the server secret + session ID)
  2. Client generates frames every 1 second: URL?f=FRAME_INDEX&t=TIMESTAMP&h=HMAC_HEX
  3. Scanner reads the current frame — server validates the HMAC, timestamp freshness (5s window), and frame index (replay detection)
  4. Stale or replayed frames are rejected server-side

Important security distinction: Scanning or screenshotting an animated frame captures a valid URL that resolves in the browser (the page loads). However, the HMAC signature embedded in the URL expires within seconds server-side. The server validates three properties: timestamp freshness (<5s), HMAC integrity (recomputed from the frame secret), and frame index monotonicity (each frame is single-use). A forwarded screenshot passes URL resolution but fails cryptographic validation — the QR is always scannable, but only the live frame is cryptographically valid.

Usage

import { AnimatedQRRenderer } from '@qrauth/animated-qr';

const renderer = new AnimatedQRRenderer({
  canvas: document.getElementById('qr') as HTMLCanvasElement,
  size: 300,
  baseUrl: session.verifyUrl,       // from POST /api/v1/animated-qr/session
  frameSecret: session.frameSecret, // per-session HMAC key
  frameInterval: 1000,              // ms between frame rotations (default: 1000)
  theme: 'light',                   // 'light' (recommended) or 'dark'
});

await renderer.start();

// React to trust signals from your fraud engine
renderer.setTrustState({ hueShift: 120, pulseSpeed: 2.5 });

// Stop animation when QR is scanned (keeps last frame visible)
renderer.freeze();

// Resume with a fresh session
renderer.stop();

Trust-Reactive Animation

The QR's visual state reflects its security status in real-time, driven by the fraud detection pipeline:

Server State Visual Behavior
Clean Calm, slow pulse. Finder patterns glow green.
Elevated scan velocity Animation accelerates, subtle amber hue shift
Fraud flagged Module jitter, red hue shift, finder patterns pulse fast
Revoked Modules dissolve away — code visually disintegrates

Dual Renderer Architecture

Two rendering engines with a shared interface (IAnimatedQRRenderer):

Engine Size GPU Best For
Canvas 2D (default) ~15 KB gzipped No Broad compatibility, kiosks, digital signage
CanvasKit (Skia WASM) ~6 MB lazy-load WebGL Premium visual effects, high-end devices

Both engines implement the full animation pipeline: frame rotation, module transitions, finder pattern glow, pulse rings, color wash, and all trust-reactive effects. The CanvasKit renderer uses hardware-accelerated Skia surfaces with automatic software fallback.

Trust Reveal UX

When a QR code is scanned and verified, a full-screen overlay fires on the verification page:

Successful verification: Shield pop-in (0.6s) → status text → organization name → crystalline visual fingerprint (staggered 0.4–0.8s delays). Color sweep from deep blue to verified green over 3 seconds. The visual fingerprint is a deterministic SVG generated from SHA256(timestamp + location + device_id + token) — unique to this exact scan.

Failed verification (fraud detected): Red flash → "FRAUDULENT CODE DETECTED" alarm with pulse animation. Escalating red color sweep. Event logged to audit trail with device fingerprint and geolocation.

Resource Usage

The renderer is designed to be lightweight:

State FPS CPU Impact
Transitions active (~300ms after frame rotation) 60 fps Moderate
Idle (between frame rotations) ~15 fps Low
Off-screen (scrolled away) 0 fps None (IntersectionObserver pauses)
Tab backgrounded 0 fps None (rAF suspended by browser)
  • Canvas 2D bundle: ~15 KB gzipped (including QR encoder)
  • CanvasKit bundle: ~6 MB WASM (lazy-loaded on first use, served from CDN)
  • Frame generation: < 5ms per frame (HMAC + QR encode + canvas draw)
  • Benchmark harness: src/benchmark.ts measures frame gen, render time, p50/p95/p99, memory allocation
  • For constrained hardware (kiosks, digital signage): use Canvas 2D with frameInterval: 2000 or higher

API Endpoints

  • POST /api/v1/animated-qr/session — Create a display session (app-authenticated). Returns { frameSecret, baseUrl, ttlSeconds }.
  • POST /api/v1/animated-qr/validate — Validate a scanned frame (public). Checks HMAC, timestamp, and replay. Returns { valid, reason? }.

Package

  • npm: @qrauth/animated-qr
  • License: MIT
  • Dependencies: qrcode (proven QR matrix generation)
  • Browser support: All modern browsers (Canvas 2D + Web Crypto API)

Ephemeral Delegated Access

Time-limited, scope-constrained sessions requiring no account creation. A developer generates a QR with embedded permissions and TTL, a guest scans it, gets a scoped session, and the session auto-expires. No credentials, no signup, no PII stored.

Use Cases

  • Hotel room controls: Guest scans QR at check-in, gets 72h access to room controls
  • Restaurant ordering: Diner scans table QR, scoped to that table's menu
  • Contractor access: IT generates a 4-hour session — no account, no offboarding
  • Event attendance: Scan entrance QR for day-pass access to event app

Usage

// Server-side: create an ephemeral session
const session = await qrauth.createEphemeralSession({
  scopes: ['read:menu', 'write:order'],
  ttl: '30m',
  maxUses: 1,
  deviceBinding: true,
  metadata: { table: 'A12' },
});
// Returns: { sessionId, token, claimUrl, expiresAt }

// Client-side: user scans the QR and claims the session
const claimed = await qrauth.claimEphemeralSession(token, {
  deviceFingerprint: '...',
});
// Returns: { sessionId, status, scopes, metadata, expiresAt }

// Revoke immediately
await qrauth.revokeEphemeralSession(sessionId);

API Endpoints

  • POST /api/v1/ephemeral — Create session (app-authenticated)
  • POST /api/v1/ephemeral/:token/claim — Claim session (public, rate-limited)
  • GET /api/v1/ephemeral/:id — Get session status (app-authenticated)
  • GET /api/v1/ephemeral — List sessions (app-authenticated)
  • DELETE /api/v1/ephemeral/:id — Revoke session (app-authenticated)

Available in Node.js SDK (createEphemeralSession, claimEphemeralSession, revokeEphemeralSession, listEphemeralSessions) and Python SDK (snake_case equivalents).


Proximity Verification API

Formalizes physical proximity as a signed attestation. When a user scans a QR code, QRAuth issues a ProximityAttestation JWT proving a specific device was physically near a specific QR code at a specific time. The attestation is verifiable by third parties without calling the QRAuth API.

How It Works

QR scanning inherently proves physical proximity — you must see the code to scan it. This is a property passwords and passkeys fundamentally cannot provide. The Proximity API makes this property cryptographically verifiable.

// After scan, retrieve the proximity attestation
const attestation = await qrauth.getProximityAttestation(scanToken, {
  clientLat: 40.6325,
  clientLng: 22.9410,
});
// Returns: { jwt, claims, publicKey, keyId }

// Third-party verification (offline, no API call needed)
const result = await qrauth.verifyProximityAttestation(attestation.jwt, publicKey);
// Returns: { valid: true, claims: { sub, loc, proximity, iat, exp } }

ProximityAttestation JWT Claims

{
  "sub": "device_id_hash",
  "iss": "qrauth.io",
  "loc": "sx3b4f",
  "proximity": { "distance": 12.4, "matched": true, "radiusM": 50 },
  "iat": 1714200000,
  "exp": 1714200300
}

Key Properties

  • Binary proximity: scanned / not scanned (not distance-based guessing)
  • Offline verification: any third party can verify with the organization's public key
  • Short TTL: 5-minute attestation window prevents replay
  • ES256 signatures: ECDSA-P256, same key infrastructure as QR signing

Use Cases

  • Attendance verification: Employee scans office QR → signed attestation proves physical presence. Replaces badge-tap systems with zero hardware cost.
  • Location-gated transactions: Approve high-value transaction only if user is physically at branch. Attestation attached to audit trail.
  • Physical access control: Scan QR at building entrance → door unlocks. No NFC reader, no badge provisioning.
  • Proof of visit: Insurance adjuster, field service tech, delivery confirmation — verifiable proof of being at a location.

API Endpoints

  • POST /api/v1/proximity/:token — Create proximity attestation (with clientLat, clientLng)
  • POST /api/v1/proximity/verify — Verify a ProximityAttestation JWT (offline-capable with public key)

Web Components

Framework-agnostic custom elements using Shadow DOM. Single <script> tag + one custom element = working auth. No npm install, no React, no build step.

<qrauth-login>

Drop-in authentication button with full PKCE flow, QR display, status polling, and event dispatching.

<script src="https://cdn.qrauth.io/v1/components.js"></script>
<qrauth-login
  tenant="your-client-id"
  theme="dark"
  scopes="identity email"
  on-auth="handleLogin">
</qrauth-login>

Features:

  • Two display modes: display="button" (opens modal) or display="inline"
  • PKCE flow: no client secret needed in the browser
  • Events: qrauth:authenticated, qrauth:expired, qrauth:error, qrauth:scanned
  • CSS custom properties for theming (--qrauth-primary, --qrauth-bg, etc.)
  • Dark theme via theme="dark" attribute

<qrauth-2fa>

Drop-in second-factor authentication. Augments an existing login flow — users keep their password/passkey, add QR as a second factor. The Trojan horse: enter as 2FA, expand to primary auth.

<qrauth-2fa
  tenant="your-client-id"
  session-token="existing-session-token"
  theme="dark">
</qrauth-2fa>

Features:

  • Step indicator ("Step 2 of 2 — Verify identity")
  • Compact 160x160 QR display with countdown timer
  • Events: qrauth:verified, qrauth:denied, qrauth:expired, qrauth:error
  • PKCE code challenge/verifier flow (S256)
  • Polling with exponential backoff (2–5s intervals)
  • auto-start attribute for immediate session creation

<qrauth-ephemeral>

Ephemeral access QR with TTL countdown and claim status tracking. No account creation required for the end user.

<qrauth-ephemeral
  tenant="your-client-id"
  scopes="read:menu write:order"
  ttl="30m"
  max-uses="1"
  device-binding>
</qrauth-ephemeral>

Features:

  • TTL countdown with live timer (supports seconds, minutes, hours, days)
  • Multi-use session support with useCount/maxUses badge
  • Two display modes: display="button" (modal) or display="inline"
  • Events: qrauth:claimed, qrauth:expired, qrauth:revoked, qrauth:error
  • Auto-close modal on claim (2.2s delay)

Bundle & Distribution

  • Package: @qrauth/web-components (~23 KB gzipped)
  • CDN: https://cdn.qrauth.io/v1/components.js (IIFE) or components.esm.js (ESM)
  • npm: npm install @qrauth/web-components
  • Components: <qrauth-login>, <qrauth-2fa>, <qrauth-ephemeral>
  • Animated QR: @qrauth/animated-qr (MIT, separate package)

QRVA Open Protocol

QRAuth is built on the QRVA (QR Verification and Authentication) open protocol specification. The protocol defines the standard for signing, verifying, and authenticating physical QR codes.

Why Open?

Auth0 succeeded because it built on open standards (OAuth 2.0, OIDC). QRAuth follows the same playbook. An open protocol:

  • Accelerates ecosystem adoption — competitors building compatible implementations expand the market
  • Builds institutional trust — governments and enterprises prefer open standards over proprietary lock-in
  • Enables standardization — an IETF Internet-Draft positions QRVA for future RFC adoption (note: standardization takes 2-5 years from initial draft)
  • QRAuth wins as the reference implementation with the best DX, trust registry, and network effects

Protocol Specification

The QRVA protocol defines:

1. QR Payload Format

https://[verifier-domain]/v/[token]

Where token is a URL-safe base64-encoded structure containing: issuer identifier, payload hash, signature, and optional metadata.

2. Signing Algorithm

  • ECDSA with P-256 curve (FIPS 186-4)
  • SHA-256 hash of canonical payload
  • 64-byte compact signature format

3. Verification Flow

  • Token resolution → Signature verification → Geospatial check → Trust score computation
  • Each step is independently cacheable at the edge

4. Geospatial Binding

  • WGS84 coordinates with accuracy radius
  • Haversine distance computation for scan-time matching

5. Transparency Log

  • Append-only Merkle tree structure
  • Inclusion proofs for any issued token
  • Compatible with Certificate Transparency (RFC 6962) tooling

6. Event Schema

  • Standardized event types: qr.created, qr.scanned, qr.verified, qr.failed, fraud.detected, fraud.proxy_detected, passkey.enrolled, passkey.verified
  • JSON event format with tenant, token, timestamp, and context

The full specification is published at docs.qrauth.io/protocol and as a standalone document in docs/PROTOCOL.md.

Reference Implementation

This repository IS the reference implementation. Third parties can build compatible verifiers and signers using the protocol spec. A compliance test suite is provided at packages/protocol-tests/ to validate compatibility.


Tech Stack

Backend

Component Technology Rationale
API Gateway Node.js + Fastify High throughput, low overhead, TypeScript-native
Verification Edge Cloudflare Workers Sub-50ms cryptographic verification, edge-cached keys
Database (relational) PostgreSQL 16 + PostGIS Geospatial queries, ACID, tenant isolation
Database (analytics) ClickHouse Columnar storage for billions of scan events
Key Management AWS KMS / HashiCorp Vault HSM-backed, FIPS 140-2 compliant
Cache Redis (Upstash for edge) Token lookup, rate limiting, session state
Event Bus Redis Streams Webhook dispatch, event sourcing
Image Generation Sharp (libvips) Server-side visual proof rendering (Fly.io API only — not compatible with Cloudflare Workers)
Queue BullMQ (Redis-backed) Async fraud detection, alert delivery
ML Pipeline Python + scikit-learn → ONNX Anomaly detection, exportable to edge

SDKs and Frontend

Component Technology Rationale
SDKs TypeScript, Python (stable); Go, PHP, Swift, Kotlin (planned) Native experience per ecosystem
Embeddable Widget Vanilla JS (badge.js) Zero dependencies, <5KB gzipped
React Component @qrauth/react First-class React integration
Tenant Portal React + TypeScript + Vite SPA with real-time dashboard
Verification Page Vanilla HTML/JS (edge-rendered) Zero deps, fast TTFB
Developer Docs Mintlify or Nextra Interactive API explorer

Mobile App

Component Technology Rationale
Framework React Native + Expo Cross-platform, shared logic with web
QR Scanner vision-camera + ML Kit Native camera, fast decode
WebAuthn react-native-passkeys Native passkey integration
NFC react-native-nfc-manager Future tamper-evident tag support
Push Expo Notifications + FCM/APNs Fraud alerts
Offline DB WatermelonDB Cached issuer keys, offline verify

Infrastructure

Component Technology Rationale
Containers Docker + Fly.io / Railway Simple deploy, global distribution
CI/CD GitHub Actions Automated test, stage, prod pipeline
Monitoring Grafana + Prometheus Service health, latency tracking
Errors Sentry Runtime error capture, all services
DNS / CDN Cloudflare Edge cache, DDoS, Workers runtime
Secrets Doppler or 1Password Connect Centralized secret management

Project Structure

qrauth/
├── README.md
├── docker-compose.yml
├── .env.example
├── turbo.json                        # Turborepo monorepo config
│
├── packages/
│   ├── api/                          # Fastify API Gateway
│   │   ├── src/
│   │   │   ├── server.ts
│   │   │   ├── routes/
│   │   │   │   ├── tenants.ts        # Tenant CRUD, onboarding, config
│   │   │   │   ├── qrcodes.ts        # QR generation + signing
│   │   │   │   ├── verify.ts         # Verification (non-edge fallback)
│   │   │   │   ├── webhooks.ts       # Webhook endpoint management
│   │   │   │   └── analytics.ts      # Dashboard data endpoints
│   │   │   ├── services/
│   │   │   │   ├── signing.ts        # ECDSA key management + signing
│   │   │   │   ├── tenants.ts        # Tenant isolation logic
│   │   │   │   ├── geo.ts            # PostGIS geospatial queries
│   │   │   │   ├── fraud.ts          # Fraud detection orchestration
│   │   │   │   ├── events.ts         # Event bus (Redis Streams)
│   │   │   │   └── alerts.ts         # Notification dispatch
│   │   │   ├── middleware/
│   │   │   │   ├── auth.ts           # JWT + API key + tenant scoping
│   │   │   │   ├── rateLimit.ts      # Per-tenant rate limiting
│   │   │   │   └── validate.ts       # Zod schema validation
│   │   │   └── lib/
│   │   │       ├── crypto.ts         # ECDSA utilities, HMAC helpers
│   │   │       ├── db.ts             # PostgreSQL connection pool
│   │   │       └── cache.ts          # Redis client
│   │   ├── prisma/
│   │   │   └── schema.prisma
│   │   ├── tests/
│   │   └── package.json
│   │
│   ├── edge/                         # Cloudflare Workers verification
│   │   ├── src/
│   │   │   ├── worker.ts             # Edge verification handler
│   │   │   ├── verify.ts             # Signature verification logic
│   │   │   ├── antiProxy.ts          # Latency + canvas checks (JA3/JA4 requires Enterprise)
│   │   │   ├── visualProof.ts        # Visual proof request (delegates to API for image gen)
│   │   │   ├── webauthn.ts           # Passkey challenge/response
│   │   │   └── geo.ts               # Location matching at edge
│   │   ├── wrangler.toml
│   │   └── package.json
│   │
│   ├── animated-qr/                  # @qrauth/animated-qr (Living Codes renderer)
│   │   ├── src/
│   │   │   ├── index.ts              # Public API exports
│   │   │   ├── qr-encoder.ts         # QR matrix generation (wraps qrcode lib)
│   │   │   ├── frame-signer.ts       # HMAC frame signing (Web Crypto API)
│   │   │   ├── renderer.ts           # Canvas 2D animated renderer
│   │   │   ├── renderer-canvaskit.ts  # CanvasKit/Skia WASM renderer (WebGL)
│   │   │   ├── renderer-interface.ts  # Shared IAnimatedQRRenderer interface
│   │   │   ├── benchmark.ts          # Performance harness (Canvas2D vs CanvasKit)
│   │   │   └── types.ts              # Type definitions
│   │   └── package.json
│   │
│   ├── web-components/               # @qrauth/web-components (Shadow DOM)
│   │   ├── src/
│   │   │   ├── base.ts               # QRAuthElement base class
│   │   │   ├── login.ts              # <qrauth-login> custom element
│   │   │   ├── twofa.ts              # <qrauth-2fa> second factor component
│   │   │   ├── ephemeral.ts          # <qrauth-ephemeral> temporary access component
│   │   │   └── index.ts
│   │   ├── scripts/
│   │   │   └── bundle.js             # esbuild bundler (IIFE + ESM output)
│   │   └── package.json
│   │
│   ├── sdk-node/                     # @qrauth/node SDK
│   │   ├── src/
│   │   │   ├── index.ts              # Main QRAuth client class
│   │   │   ├── signing.ts            # QR creation methods
│   │   │   ├── verification.ts       # Verify methods
│   │   │   ├── webhooks.ts           # Webhook event consumer
│   │   │   └── types.ts              # Public TypeScript interfaces
│   │   ├── tests/
│   │   └── package.json
│   │
│   ├── sdk-python/                   # qrauth Python SDK
│   │   ├── qrauth/
│   │   │   ├── __init__.py
│   │   │   ├── client.py
│   │   │   ├── signing.py
│   │   │   ├── verification.py
│   │   │   └── webhooks.py
│   │   ├── tests/
│   │   └── pyproject.toml
│   │
│   ├── sdk-go/                       # qrauth-go SDK
│   │   ├── qrauth.go
│   │   ├── signing.go
│   │   ├── verification.go
│   │   ├── webhooks.go
│   │   └── go.mod
│   │
│   ├── sdk-php/                      # qrauth-php SDK
│   │   ├── src/
│   │   │   ├── QRAuth.php
│   │   │   ├── Signing.php
│   │   │   ├── Verification.php
│   │   │   └── Webhooks.php
│   │   ├── tests/
│   │   └── composer.json
│   │
│   ├── badge/                        # @qrauth/badge.js (embeddable widget)
│   │   ├── src/
│   │   │   ├── badge.ts              # Embeddable verification badge
│   │   │   ├── styles.ts             # Inline styles (no CSS deps)
│   │   │   └── api.ts                # Verification API client
│   │   └── package.json
│   │
│   ├── react/                        # @qrauth/react component
│   │   ├── src/
│   │   │   ├── QRAuthBadge.tsx       # React verification badge
│   │   │   ├── QRAuthProvider.tsx     # Context provider
│   │   │   └── hooks.ts              # useQRAuth, useVerification
│   │   └── package.json
│   │
│   ├── portal/                       # Tenant dashboard (React SPA)
│   │   ├── src/
│   │   │   ├── pages/
│   │   │   │   ├── Dashboard.tsx     # Overview: scans, fraud, health
│   │   │   │   ├── QRCodes.tsx       # QR management + generation
│   │   │   │   ├── Locations.tsx     # Geospatial deployment map
│   │   │   │   ├── Analytics.tsx     # Scan patterns, heatmaps
│   │   │   │   ├── Fraud.tsx         # Fraud incident log
│   │   │   │   ├── Webhooks.tsx      # Webhook config + event log
│   │   │   │   ├── APIKeys.tsx       # API key management
│   │   │   │   ├── Branding.tsx      # Custom verification page styling
│   │   │   │   ├── Team.tsx          # Team members, roles, SSO
│   │   │   │   └── Settings.tsx      # Tenant config, billing
│   │   │   ├── components/
│   │   │   ├── hooks/
│   │   │   └── lib/
│   │   ├── tests/
│   │   └── package.json
│   │
│   ├── mobile/                       # React Native mobile app
│   │   ├── app/
│   │   │   ├── (tabs)/
│   │   │   │   ├── scan.tsx          # QR scanner + instant verification
│   │   │   │   ├── history.tsx       # Scan history + fraud reports
│   │   │   │   ├── alerts.tsx        # Push notification center
│   │   │   │   └── settings.tsx      # Passkey management, preferences
│   │   │   ├── verify/
│   │   │   │   └── [token].tsx       # Deep-link verification screen
│   │   │   └── _layout.tsx
│   │   ├── lib/
│   │   │   ├── crypto.ts            # Shared ECDSA verification
│   │   │   ├── offlineCache.ts      # Cached keys (WatermelonDB)
│   │   │   ├── passkeys.ts          # WebAuthn integration
│   │   │   └── nfc.ts              # NFC tag reading (future)
│   │   └── package.json
│   │
│   ├── protocol-tests/              # QRVA compliance test suite
│   │   ├── src/
│   │   │   ├── signing.test.ts      # Signing compliance tests
│   │   │   ├── verification.test.ts # Verification compliance tests
│   │   │   ├── geo.test.ts          # Geospatial compliance tests
│   │   │   └── events.test.ts       # Event schema compliance tests
│   │   └── package.json
│   │
│   └── shared/                      # Shared types, constants, utilities
│       ├── src/
│       │   ├── types.ts             # TypeScript interfaces
│       │   ├── constants.ts         # Protocol constants
│       │   ├── crypto.ts            # Shared crypto utilities
│       │   ├── events.ts            # Event type definitions
│       │   └── validation.ts        # Shared Zod schemas
│       └── package.json
│
├── infra/
│   ├── docker/
│   │   ├── Dockerfile.api
│   │   ├── Dockerfile.portal
│   │   └── Dockerfile.mobile
│   ├── terraform/
│   │   ├── main.tf
│   │   ├── database.tf
│   │   ├── kms.tf
│   │   └── monitoring.tf
│   └── ansible/
│       └── playbooks/
│
├── docs/
│   ├── PROTOCOL.md                  # QRVA protocol specification
│   ├── SECURITY.md                  # Security model deep-dive
│   ├── API.md                       # Full API documentation
│   ├── SDK_GUIDE.md                 # SDK integration guide
│   ├── WEBHOOKS.md                  # Webhook event reference
│   ├── MULTI_TENANT.md              # Tenant architecture guide
│   ├── DEPLOYMENT.md                # Self-hosted deployment guide
│   ├── THREAT_MODEL.md              # Threat modeling document
│   └── COMPLIANCE.md                # SOC2, GDPR, ISO compliance
│
└── scripts/
    ├── setup.sh                     # Local development setup
    ├── seed.sh                      # Seed database with test data
    ├── generate-keys.sh             # Generate test ECDSA key pairs
    └── publish-sdks.sh              # Publish all SDKs to registries

API Reference

Authentication

All API endpoints are tenant-scoped. Authenticate via Bearer token (JWT) or API key.

Authorization: Bearer <jwt_token>
# or
X-API-Key: <api_key>

All requests are scoped to the authenticated tenant. Cross-tenant access is not possible.

Endpoints

Tenants

POST   /api/v1/tenants                     # Create new tenant
GET    /api/v1/tenants/:id                  # Get tenant config
PATCH  /api/v1/tenants/:id                  # Update tenant settings
POST   /api/v1/tenants/:id/verify           # Submit KYC verification
GET    /api/v1/tenants/:id/keys             # List signing keys
POST   /api/v1/tenants/:id/keys/rotate      # Rotate signing key
PATCH  /api/v1/tenants/:id/branding         # Update verification page branding

QR Codes

POST   /api/v1/qrcodes                     # Generate signed QR code
GET    /api/v1/qrcodes                     # List tenant's QR codes (paginated)
GET    /api/v1/qrcodes/:token              # Get QR code details
PATCH  /api/v1/qrcodes/:token              # Update destination URL
DELETE /api/v1/qrcodes/:token              # Revoke QR code
POST   /api/v1/qrcodes/bulk               # Bulk generate (up to 1000)

Verification (Public — No Auth Required)

GET    /v/[token]                          # Web verification page (edge)
GET    /api/v1/verify/:token               # API verification (for SDKs)
POST   /api/v1/verify/:token/webauthn      # WebAuthn challenge/response

Webhooks

POST   /api/v1/webhooks                    # Register webhook endpoint
GET    /api/v1/webhooks                    # List configured webhooks
PATCH  /api/v1/webhooks/:id               # Update webhook
DELETE /api/v1/webhooks/:id               # Remove webhook
GET    /api/v1/webhooks/:id/logs          # View delivery logs
POST   /api/v1/webhooks/:id/test          # Send test event
POST   /api/v1/events/replay              # Replay events (time range)

Analytics

GET    /api/v1/analytics/scans            # Scan event history
GET    /api/v1/analytics/heatmap          # Geographic scan heatmap
GET    /api/v1/analytics/fraud            # Fraud incident log
GET    /api/v1/analytics/summary          # Dashboard summary stats
GET    /api/v1/analytics/export           # CSV/JSON export

Proximity Attestation

POST   /api/v1/proximity/:token               # Create proximity attestation (with clientLat, clientLng)
POST   /api/v1/proximity/verify               # Verify ProximityAttestation JWT (offline-capable)

Transparency Log (Public — No Auth Required)

GET    /api/v1/transparency/log           # Query transparency log
GET    /api/v1/transparency/proof/:token  # Get inclusion proof for token

Example: Generate a Signed QR Code

curl -X POST https://api.qrauth.io/api/v1/qrcodes \
  -H "X-API-Key: qra_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "destination_url": "https://parking.thessaloniki.gr/pay/zone-b",
    "label": "Parking Zone B - Tsimiski Street",
    "location": {
      "lat": 40.6321,
      "lng": 22.9414,
      "radius_m": 15
    },
    "metadata": {
      "zone": "B",
      "rate": "2.00/hr"
    },
    "expires_at": "2027-01-01T00:00:00Z"
  }'

Response:

{
  "token": "xK9m2pQ7",
  "verification_url": "https://qrauth.io/v/xK9m2pQ7",
  "qr_image_url": "https://api.qrauth.io/qr/xK9m2pQ7.png",
  "signature": "MEUCIQD...base64...==",
  "tenant_id": "ten_parking_thess",
  "created_at": "2026-04-01T12:00:00Z",
  "expires_at": "2027-01-01T00:00:00Z",
  "transparency_log_index": 48291
}

Example: Verify (API)

curl https://api.qrauth.io/api/v1/verify/xK9m2pQ7 \
  -H "X-Client-Lat: 40.6325" \
  -H "X-Client-Lng: 22.9410"

Response:

{
  "verified": true,
  "issuer": {
    "tenant_id": "ten_parking_thess",
    "name": "Municipality of Thessaloniki",
    "verified_since": "2026-03-15T00:00:00Z",
    "trust_level": "government"
  },
  "destination_url": "https://parking.thessaloniki.gr/pay/zone-b",
  "location_match": {
    "matched": true,
    "distance_m": 12,
    "registered_address": "Tsimiski Street, Zone B"
  },
  "security": {
    "signature_valid": true,
    "proxy_detected": false,
    "trust_score": 94,
    "transparency_log_verified": true
  },
  "metadata": {
    "zone": "B",
    "rate": "2.00/hr"
  },
  "scanned_at": "2026-04-01T14:32:07Z"
}

Embeddable Verification Widget

The verification widget is QRAuth's equivalent of Auth0's Lock. It's a drop-in component that renders a verification badge inside the partner's application. The user never leaves the partner's app — trust is established inline.

JavaScript (Vanilla)

<!-- Minimal: just the badge -->
<script src="https://cdn.qrauth.io/badge.js"></script>
<div data-qrauth-badge="xK9m2pQ7"></div>

<!-- Full configuration -->
<script src="https://cdn.qrauth.io/badge.js"></script>
<div
  data-qrauth-badge="xK9m2pQ7"
  data-theme="dark"
  data-show-location="true"
  data-show-issuer="true"
  data-locale="el"
></div>

Programmatic API

const badge = QRAuth.badge('xK9m2pQ7', {
  container: document.getElementById('verify-container'),
  theme: 'dark',
  locale: 'el',
  onVerified: (result) => {
    console.log('Verified:', result.issuer.name);
    window.location.href = result.destination_url;
  },
  onFailed: (error) => {
    showAlert('This QR code could not be verified.');
  },
  onFraudDetected: (alert) => {
    reportToAdmin(alert);
  },
});

Widget Size

  • badge.js: <5KB gzipped
  • Zero external dependencies
  • No CSS files — all styles are inline/shadow DOM
  • Works in all modern browsers (Chrome 80+, Safari 14+, Firefox 78+, Edge 80+)

Mobile App

The mobile app is the highest-security verification channel — the dedicated verification terminal for physical access infrastructure and power users. For developers integrating QRAuth into their own applications, the SDKs and APIs are the primary interface.

Capabilities Beyond Web

  • Native QR scanning with real-time verification overlay (verify before opening any URL)
  • WebAuthn passkey management with biometric binding (Face ID, fingerprint)
  • Offline verification using cached tenant public keys (no internet at scan time)
  • Push notifications for fraud alerts near the user's location
  • NFC tag reading for future tamper-evident physical verification
  • Scan history with fraud reporting

Offline Verification Flow

1. App periodically syncs tenant public keys → stores in WatermelonDB
2. User scans QR code (no internet required)
3. App parses QRAuth token from URL
4. App verifies ECDSA signature against cached public key
5. App checks GPS against cached geospatial data
6. Displays verification result with "offline" indicator
7. When connectivity returns, uploads scan event for analytics

Platform Support

Platform Minimum Version Passkey Support NFC Support
iOS 16.0+ Yes (Keychain) Yes (Core NFC)
Android 9.0+ (API 28) Yes (FIDO2) Yes (NfcAdapter)

Multi-Tenant Architecture

Every QRAuth customer operates in a fully isolated tenant. This is the foundation that enables enterprise pricing and compliance.

Tenant Isolation

Tenant: parking-thessaloniki
├── Signing Keys (KMS-isolated, auto-rotatable)
├── QR Code Registry (namespace-scoped)
├── Geospatial Data (PostGIS, schema-isolated)
├── WebAuthn User Pool (independent credential store)
├── Webhook Endpoints (tenant-specific)
├── Analytics Partition (ClickHouse, partition key = tenant_id)
├── Branding Config (logo, colors, verification page copy)
├── Rate Limits (per-tier, configurable)
├── API Keys (multiple per tenant, scoped permissions)
└── Team Members (roles: owner, admin, developer, viewer)

Custom Verification Page Branding

Enterprise tenants can customize the verification page appearance:

{
  "branding": {
    "logo_url": "https://thessaloniki.gr/logo.png",
    "primary_color": "#1E3A8A",
    "background_color": "#F8FAFC",
    "display_name": "Thessaloniki Parking",
    "support_url": "https://thessaloniki.gr/support",
    "custom_css": "/* optional CSS overrides */"
  }
}

The verification page at qrauth.io/v/[token] renders with the tenant's branding while maintaining QRAuth's trust indicators (the QRAuth seal is always visible to establish trust chain).

SSO / SAML (Enterprise)

Enterprise tenants can configure SSO for their team members:

  • SAML 2.0 (Okta, Azure AD, Google Workspace)
  • OIDC (any compliant provider)
  • SCIM provisioning for automated user lifecycle

Webhooks and Events

Every platform action emits an event. Tenants configure webhook endpoints to receive events in real-time, enabling automated workflows (Slack alerts, analytics pipelines, incident response).

Event Types

Event Description Payload
qr.created New QR code generated token, destination, location, metadata
qr.updated QR code destination changed token, old_destination, new_destination
qr.revoked QR code revoked token, reason
qr.expired QR code reached expiry token
scan.completed QR code scanned and verified token, scanner_location, trust_score
scan.failed Verification failed token, reason, scanner_location
fraud.detected Anomaly detected token, alert_type, confidence, details
fraud.proxy_detected Proxy/MitM detected token, ja3_hash, latency_ms
passkey.enrolled User created a passkey user_id, device_type
passkey.verified Passkey verification succeeded user_id, token

Webhook Delivery

  • Format: JSON POST to configured HTTPS endpoint
  • Signing: Every webhook is signed with HMAC-SHA256(webhook_secret, payload) in the X-QRAuth-Signature header
  • Retry: Exponential backoff (1s, 5s, 30s, 5m, 30m, 2h) on failure
  • Dead Letter: Failed events after all retries are stored for 30 days (replayable)
  • Filtering: Subscribe to specific event types per endpoint

Example Webhook Payload

{
  "id": "evt_a1b2c3d4",
  "type": "fraud.detected",
  "tenant_id": "ten_parking_thess",
  "created_at": "2026-04-01T14:32:07Z",
  "data": {
    "token": "xK9m2pQ7",
    "alert_type": "duplicate_location",
    "confidence": 0.92,
    "details": {
      "registered_destination": "https://parking.thessaloniki.gr/pay/zone-b",
      "scanned_destination": "https://parkng-thessaloniki.gr/pay",
      "location": { "lat": 40.6321, "lng": 22.9414 },
      "scanner_ip_country": "GR"
    }
  }
}

Verifying Webhook Signatures (Node.js SDK)

import { QRAuth } from '@qrauth/node';

const qrauth = new QRAuth({ webhookSecret: process.env.QRAUTH_WEBHOOK_SECRET });

app.post('/webhooks/qrauth', (req, res) => {
  const event = qrauth.webhooks.verify(
    req.body,
    req.headers['x-qrauth-signature']
  );

  switch (event.type) {
    case 'fraud.detected':
      slackAlert(`Fraud detected on ${event.data.token}: ${event.data.alert_type}`);
      break;
    case 'scan.completed':
      analyticsTrack('qr_scan', event.data);
      break;
  }

  res.status(200).send('ok');
});

Getting Started (Development)

Prerequisites

  • Node.js 20+
  • PostgreSQL 16 with PostGIS extension
  • Redis 7+
  • Docker (optional, for containerized development)
  • Turborepo (npm install -g turbo)

Local Setup

# Clone
git clone https://github.com/qrauth-io/qrauth.git
cd qrauth

# Install dependencies (monorepo)
npm install

# Copy environment variables
cp .env.example .env

# Generate development ECDSA key pair
./scripts/generate-keys.sh

# Start PostgreSQL + Redis + ClickHouse
docker compose up -d

# Run database migrations
turbo run db:migrate --filter=api

# Seed with test data
turbo run db:seed --filter=api

# Start all services in development mode
turbo run dev

This starts:

  • API Gateway: http://localhost:3000
  • Tenant Portal: http://localhost:5173
  • Verification Page: http://localhost:3000/v/[token]
  • Developer Docs: http://localhost:3001

Environment Variables

# Core
NODE_ENV=development
PORT=3000

# Database
DATABASE_URL=postgresql://qrauth:qrauth@localhost:5432/qrauth
REDIS_URL=redis://localhost:6379
CLICKHOUSE_URL=http://localhost:8123

# Cryptography
KMS_PROVIDER=local                         # local | aws | vault
ECDSA_PRIVATE_KEY_PATH=./keys/dev.pem      # Local dev only
VISUAL_PROOF_SECRET=<random-64-chars>

# WebAuthn
WEBAUTHN_RP_NAME=QRAuth
WEBAUTHN_RP_ID=localhost
WEBAUTHN_ORIGIN=http://localhost:3000

# Cloudflare (production only)
CF_ACCOUNT_ID=
CF_API_TOKEN=

# Billing
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# Alerts
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=

Deployment

Production Architecture

                     ┌──────────────┐
                     │  Cloudflare   │
                     │  DNS + CDN    │
                     └──────┬───────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
    ┌─────────▼───┐  ┌──────▼──────┐  ┌───▼──────────┐
    │ CF Workers  │  │ Fly.io /    │  │ Cloudflare   │
    │ (verify     │  │ Railway     │  │ Pages        │
    │  edge)      │  │ (API +      │  │ (Portal +    │
    │             │  │  services)  │  │  Docs)       │
    └─────────────┘  └──────┬──────┘  └──────────────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
    ┌─────────▼───┐  ┌──────▼──────┐  ┌───▼──────────┐
    │ PostgreSQL  │  │ Redis       │  │ ClickHouse   │
    │ + PostGIS   │  │ (Upstash)   │  │ Cloud        │
    │ (Neon)      │  │             │  │              │
    └─────────────┘  └─────────────┘  └──────────────┘

Infrastructure Cost (MVP)

Service Provider Monthly Cost
Edge Verification Cloudflare Workers (free tier) $0
API + Services Fly.io (2x shared-cpu) $10-20
PostgreSQL + PostGIS Neon (free tier) $0
Redis Upstash (free tier) $0
Analytics ClickHouse Cloud (dev) $25
Portal + Docs Cloudflare Pages (free) $0
Domain (qrauth.io) Registrar ~$30/year
Total MVP $35-75/month

Compliance

QRAuth is a security product. Enterprise buyers in regulated industries will not evaluate without certifications.

Planned Certifications

Certification Target Date Purpose
SOC 2 Type I Q4 2026 Baseline security controls attestation
SOC 2 Type II Q2 2027 Operational effectiveness over time
GDPR Compliance Launch (Q3 2026) EU data protection (mandatory for EU market)
ISO 27001 Q4 2027 Information security management system
eIDAS Compatibility 2028 EU electronic identification (government tier)

Data Handling

  • All scan data is encrypted at rest (AES-256) and in transit (TLS 1.3)
  • Tenant data is logically isolated (schema-level in PostgreSQL, partition-level in ClickHouse)
  • PII minimization: scanner IP addresses are hashed before storage
  • Data residency: EU tenants can specify EU-only data storage (Fly.io + Neon EU regions)
  • Retention policies: configurable per tenant (default 90 days for scan events)
  • Right to erasure: tenant deletion purges all associated data within 30 days

Status

QRAuth is in active development. The core platform, SDKs, and all major subsystems are shipped and running in production.

Shipped: ECDSA-P256 signing, WebAuthn passkeys, device trust registry, 6-signal fraud detection, Living Codes (animated QR), ephemeral delegated access, proximity attestation, web components, Node.js + Python SDKs, multi-tenant dashboard, transparency log, background workers.

See the changelog for release history.


Contributing

We welcome contributions — whether it's a bug report, feature request, SDK in a new language, or a fix. See CONTRIBUTING.md for setup instructions, code standards, and how to submit a PR.

Good first issues are tagged good first issue.


License

This project is licensed under the BSL 1.1 (Business Source License) — free for non-commercial use, with automatic conversion to Apache 2.0 after 4 years. Commercial use requires a license from QRAuth.

The QRVA protocol specification (docs/PROTOCOL.md) is licensed under CC BY 4.0 — freely usable by anyone.


Links


Cryptographic verification infrastructure for every QR code in the physical world.