QR Code Security
SingleForm protects against QR code spoofing with cryptographic verification tokens. Every QR code includes a token that the mobile app validates before loading the form, ensuring the QR code was generated by the form’s owner.
Why QR Verification Matters
Without verification, an attacker could create a QR code that points to a legitimate SingleForm form ID but includes malicious metadata or redirects users to a phishing form. The verification token proves the QR code was generated by someone with access to the form’s signing secret.
How It Works
Token Generation
Each form gets a deterministic 8-character verification token:
- Compute
HMAC-SHA256(formId, QR_VERIFY_SECRET) - Map the first 8 bytes of the HMAC digest to characters from
A-Z0-9 - Embed the token as a
?v=query parameter in the QR code URL
The token is deterministic — the same form ID always produces the same token. This means all QR codes for a given form share the same verification token regardless of when they were generated.
Token Validation
When the mobile app scans a QR code:
- Extract the
vparameter from the URL - The app sends the form ID and token to the backend
- The backend recomputes
HMAC-SHA256(formId, QR_VERIFY_SECRET)and derives the expected token - Tokens are compared using a timing-safe comparison to prevent timing attacks
- If the token doesn’t match, the app warns the user that the QR code may be spoofed
The QR_VERIFY_SECRET environment variable must be set on your backend for QR verification to work. If it’s not configured, verification is gracefully disabled — QR codes will still work, but without spoofing protection.
Token Format
- Length: 8 characters
- Character set:
A-Z,0-9(36 possible characters) - Case insensitive: tokens are uppercased before comparison
- Example:
K7M2QX9A
Security Properties
| Property | Detail |
|---|---|
| Algorithm | HMAC-SHA256 |
| Timing-safe | Uses crypto.timingSafeEqual to prevent timing side-channels |
| Deterministic | Same form ID always produces the same token |
| Forgery resistant | Requires knowledge of the server-side QR_VERIFY_SECRET |
| Graceful degradation | QR codes work without the secret, just without verification |
Spoof Detection
When the backend detects an invalid verification token, it:
- Logs a warning with the form ID and the invalid token
- Returns a
qrVerified: falseflag to the mobile app - The app displays a warning to the user before loading the form
This lets users make an informed decision about whether to proceed.
Relationship to Webhook Security
QR verification and webhook signature verification serve different purposes:
| QR Verification | Webhook Signatures | |
|---|---|---|
| Protects | Users scanning QR codes | Your webhook endpoint |
| Threat | Spoofed QR codes with fake metadata | Forged webhook requests |
| Algorithm | HMAC-SHA256 (8-char token) | HMAC-SHA256 (full hex digest) |
| Secret | QR_VERIFY_SECRET | SINGLEFORM_SECRET (webhook secret) |
| Validated by | SingleForm mobile app | Your server |
Both use HMAC-SHA256 but with separate secrets for defense in depth.