Login is a hot path. A real user will hit it dozens of times a month, so your error budget for false positives is small. The right pattern is a narrow action-time check at password submit — not a page-load probe — paired with a step-up challenge on
inconclusive rather than a block.The threat
Credential-stuffing tools replay dumps of leakedemail:password pairs against login endpoints at high volume. The goal is account takeover: any hit becomes a compromised account. Unlike signup, the attacker isn’t trying to create new identity — they’re trying to prove that an existing identity is theirs. That changes the policy calculus.
The pattern to detect is automated typing and submission: a headless browser driving a form fill, or an HTTP-level script bypassing the UI entirely. Tripwire’s automation attribution (Playwright/Puppeteer/Selenium) and ai-agent attribution (LLM-driven login) both land here.
Two properties of the login surface shape the integration:
- Users arrive hot. They click a link in an email, they open a bookmark, they come back from a timeout. Fingerprint readiness at page load is aspirational, not guaranteed.
- False positives are expensive. Locking a human out of their own account is a worse user experience than letting a bot try three times against a non-matching password. The verdict is one input among several — Tripwire goes on one side of a scale that already has rate limiting and MFA on it.
The flow
Start Tripwire on page load
Collection begins. Don’t block the form on
waitForFingerprint() — users arriving cold should still be able to submit.Call getSession() at password submit
Not on page load. You want the freshest possible observation set, and you want collection to include the keystroke and mouse signals from the user typing their password.
Verify and fold into your auth decision
A
bot verdict returns the same generic error as a wrong password. An inconclusive verdict with an otherwise-valid password triggers a step-up challenge.Client integration
CallgetSession() lazily at password submit. Don’t await waitForFingerprint() — if fingerprinting has resolved by submit, great; if not, Tripwire still returns a session based on the snapshot and behavioral evidence it has.
Server verification
Decisioning policy
| Verdict | Recommended action on login |
|---|---|
human + password matches | Issue a session. |
human + password mismatch | Return generic Invalid credentials. |
inconclusive + password matches | Require a second factor (TOTP, email OTP, WebAuthn). |
inconclusive + password mismatch | Return generic Invalid credentials. |
bot | Return generic Invalid credentials. Always. |
- Always check the password. Skipping the bcrypt compare on a
botverdict creates a timing oracle. Run the comparison, throw the result away if Tripwire blocks. - Return identical responses for bot-detected and password-mismatch. Status code, body shape, timing. Any difference is signal for an attacker automating against your endpoint.
- Prefer step-up over block on
inconclusive. If the password is correct and the verdict is ambiguous, a legitimate user can pass a TOTP prompt. A bot running on a dump of leaked credentials usually can’t.
Rate-limiting by visitor fingerprint
The verified token exposesvisitor_fingerprint.id — a durable per-device identifier that survives cookie clears, incognito mode, and residential-proxy IP rotation. It’s the right key for login attempt counters.
Node.js
visitor_fingerprint is null on sessions where Tripwire couldn’t establish a durable ID (typically: hardened privacy browsers, very short sessions). Treat a missing visitor ID as “skip the fingerprint rate-limiter, rely on IP” rather than as a signal to block.What’s next
Signup protection
Block automated account creation.
Server verification
Reference for the underlying verification primitive.
Going to production
Rollout plan and monitoring.
Verdicts & scoring
Understand what
inconclusive actually means.