Security & Best Practices
NexloLabs Verification was designed with security first. This page explains the built-in protections and what you should do on your side.
Built-in protections
Signed tokens (HMAC)
Verification tokens are signed with an HMAC-SHA256 secret that only the API server knows. A token cannot be forged, modified or re-signed by a client. Each token carries:
- the site ID it was issued for (
TOKEN_SITE_MISMATCHotherwise) - an expiry timestamp (
TOKEN_EXPIREDafter 120 s) - a unique nonce for replay detection
Replay protection
Redeeming a token atomically marks its hash as used in Redis (token:used:<sha256>). A second attempt with the same token fails with TOKEN_REPLAYED — this blocks session-replay attacks on form submissions.
Secret verification
The verification endpoint requires your site secret, which never leaves your backend. Without the secret, nobody can check tokens — including the site owner's own visitors.
Domain whitelist
When a challenge is created, the API validates the Host/Origin against the site's registered domains. example.com does not accept tokens from evil-example.com (suffix attacks are prevented with exact subdomain matching). Requests from unregistered hostnames fail with DOMAIN_NOT_ALLOWED before any challenge is created.
Rate limiting
Every public endpoint is rate limited per IP (and per secret for verify). Excessive requests receive 429 RATE_LIMITED, which slows down brute-force and automated traffic.
Challenge integrity
- Challenges are single-use, expire quickly and fail after 3 wrong attempts.
- Answers for
image_selectare derived deterministically from a per-challenge seed and validated server-side — the correct answer is never stored in plaintext. - Puzzle answers are validated with a positional tolerance, and the behavioral snapshot is scored together with the answer.
Session security
- Passwords hashed with bcrypt.
- TOTP secrets for 2FA are stored AES-256-GCM-sealed.
- Refresh tokens are single-use with rotation; access tokens are short-lived.
- CSRF protection for all state-changing dashboard endpoints.
No personal data
The widget collects behavioral signals (timing, interaction counts, entropy) but no identifying data: no cookies, no fingerprinting, no IPs stored per challenge beyond the standard verification log.
Best practices on your side
- Never expose the site secret. Only your backend may call
verify. Frontend code containingnlx_secret_is a leak. - Set a threshold. Accept
score >= 0.5for normal forms; use>= 0.7for high-value actions like password changes or payments. - Always verify server-side. The widget only produces tokens; trusting a token without verification defeats the purpose.
- Handle 429s gracefully. Show a friendly "too many attempts" message and retry after a few seconds.
- Re-check on repeat actions. A token is single-use — require a fresh token for each submission, e.g. per checkout step.
- Keep the widget up to date by loading it from the hosted URL, so you automatically receive improvements.
- Rotate secrets when a team member leaves or a secret might have leaked (Dashboard → Sites → Rotate secret).
- Monitor your statistics. A sudden drop in pass rate or a spike in failed attempts often indicates an attack.
- Use API keys with minimal scopes for integrations (
statistics:readonly, if that is all you need). - Set up monitoring for
GET /api/health— it reports database and Redis status.
Threat model summary
| Attack | Defense |
|---|---|
| Token forgery | HMAC signature |
| Token replay | One-time redemption, Redis |
| Form spam with headless bots | Behavioral scoring + score threshold |
| Cross-site key abuse | Domain whitelist + hostname validation |
| Brute force / scraping | Rate limits per IP and per secret |
| Answer extraction | Seeded, server-validated answers; 3 attempts max |
| Session hijacking | Short-lived access tokens, rotating refresh, CSRF |