What is step-up authentication (risk-based 2FA)?
Step-up authentication, also called risk-based or adaptive authentication, raises the verification bar only for logins that look risky, instead of forcing a second factor on every user. A risk signal at sign-in — an unfamiliar device, a masked connection, an impossible location — triggers an extra challenge such as an OTP or a stronger verification path, while routine logins pass with no friction.How ShieldLabs surfaces it
ShieldLabs scores each login as a Risk Score (0–100) with the anonymity signals behind it, and resolves the session to a set of identifiers. Three things drive the gate: the UserHID (your hashed account id, the account signing in), the durable, server-derived DeviceID, and the session’s anonymity. The DeviceID is the anchor — it survives a cookie clear, incognito, and IP rotation, so a familiar machine stays familiar and a new one stands out even when the attacker resets everything visible in the browser. ShieldLabs returns the score and the signals; the allow, require-2FA, or hold-for-verification decision is logic you write in your login flow.Gate the login on the score
The rule your code applies: read the session’srisk_score and per-signal weight, then walk a threshold ladder — below 30 issue the session, 30 to 59 require a second factor, 60 and up route to your strongest verification or hold and alert. Pair the score with the account’s history: a brand-new DeviceID, or a new country read from local_ip.country (the real network behind any VPN, where public_ip.country is whatever the exit advertises), is a stronger step-up trigger than the score alone. When the two countries disagree, detection_flags.ip_mismatch is set to true. The outcome: a familiar device with a clean score passes untouched, while a Medium-or-High score, a new DeviceID, or an active ip_mismatch flag on a previously-clean account steps up.
Build it
Create a ShieldLabs account and get your keys
Sign up for free and get 5,000 identifications, or log in if you already have an account. Register the domain you want to identify visitors on, then open the Keys page. Use the Public Key to initialize the snippet in the browser, and keep your server-side credentials on your backend: the Private API Key authenticates the History API, and each webhook endpoint has its own
whsec_… signing secret. See Keys.Check the session at the login page
Add the snippet to your login page. Once you know which account is signing in (for example after the username field), pass its hashed id to The first callback arg is the client IP the server saw for this call, not a score;
checkAuthenticatedUser — never a raw email or user id. For a high-value account or a sensitive flow, use forceCheckAuthenticatedUser at submit instead: it clears the session and runs a fresh identify call immediately, so you score the login as it is now rather than a stale read from page load.login.html
requestID is your join key to the webhook you receive server-side. Installing the snippet covers the React, Vue, Angular, Preact, and Svelte versions of the same pattern.Receive the webhook and cache it by RequestID
ShieldLabs POSTs one signed webhook per scored identification. Verify
X-Shield-Signature on the raw body, then cache the result keyed by request_id so the login request can read it back. That handler is the shared scoreCache / waitForScore helper defined once in the Use Case Tutorials; the device_id, country, and user_hid you compare below all ride in on the same flat payload. Delivery is at-most-once with no retries, so for a guaranteed read fall back to the History API by request_id (or user_hid to also pull the account’s recent sessions). Each returned History row bills 1 request and the webhook is free, so reserve History reads for the borderline logins.Walk your threshold ladder
Wait briefly for the score, then branch. The band is your starting point; the
signals array refines it. Below is a three-rung ladder you can tune to your own traffic. The actions (require 2FA, route to strong verification, hold and alert) all run in your application — ShieldLabs only returns the score and its signals.api/login.js
waitForScore polls the shared webhook cache, then falls back to a History API read by request_id.The threshold ladder, band by band
The four bands and their ranges are defined once in Risk Scoring, and the cross-scenario action playbook lives in Acting on the Risk Score. Mapped to a login gate, a sensible starting ladder is:| Band | Suggested login action |
|---|---|
| Clean (0–9) | Issue the session, no friction |
| Low (10–29) | Allow, log the signals |
| Medium (30–59) | Require a second factor (OTP, authenticator) |
| High (60–100) | Route to your strongest verification, or hold and alert the account owner |
Signals worth weighting at login
The Risk Score already folds these in. If you want to raise friction even at a moderate score when a heavily weighted signal is present, look at each entry’sweight (its numeric contribution) rather than matching the signal label text, which can change. The names below are the customer-facing labels that appear in the array, shown for context, and the Signals reference lists the full set with what each one means.
Signal in signals | Why it matters at login |
|---|---|
| Tor | Connection exits through the Tor network. Rare for a legitimate sign-in. Usually a strong-verification path decided by you. |
| JavaScript Disabled | The login ran without the script environment a real browser provides (headless or automation). Near-certain non-human login traffic; on its own it lands in the High band. |
| Anti-detect Browser | Fingerprint-spoofing indicators. A common shape behind credential-stuffing follow-up and account takeover. |
| Abuser Flag | IP or device on an abuse reputation list. Treat as high risk even at a moderate score. |
| OS Mismatch | The OS the browser claims does not match other evidence. A spoofing indicator. |
| VPN / Privacy Relay | Common for legitimate, privacy-conscious users. Weaker evidence on its own. Weigh with the rest of the signals, do not gate on it alone. |
detection_flags object — detection_flags.tor, detection_flags.anti_detect_browser, detection_flags.ip_mismatch, and so on — so you can branch without inspecting the signals array.
Pull an account's recent sessions
{ data, total } envelope; data is an array of snapshots (newest first), each in snake_case carrying device_id, country (the public IP’s country), score, and score_details (a JSON string you parse for the signal list) for that session. The shared shieldHistory helper returns this data array for you. A new device_id and country combined on an established account is the shape behind the account-takeover pattern. Account History reads on account.shieldlabs.ai are free; the Management History path bills 1 request per returned row, so reserve any billed reads for the borderline logins.
Honest caveat
Test it
Confirm the durable identity before you wire thresholds to real friction. Sign in once and note theDeviceID on the webhook, then reproduce the “new arrival” without a new device:
- Clear cookies and storage, then sign in again. The
CookieIDandVisitorIDchange, but theDeviceIDstays the same. - Open an incognito or private window and sign in. The same
DeviceIDreturns. - Switch networks (or turn on a VPN) so your IP changes, then sign in. The
DeviceIDholds; thesignalsarray now also carries the masking signal (for example VPN), which raises thescore.
DeviceID, which is the shape your ladder should step up on. This is the test that proves a fresh cookie or a rotated IP cannot fake a familiar device.
Next steps
Acting on the Risk Score
The full per-band decision playbook, including signal-aware decisioning and how to combine the score with specific signals.
Signals
Every signal that can appear in
signals, in plain language, with its weight.The Risk Score
How the 0 to 100 score is built, what
signals carries, and the band definitions.Checkout and Payment Protection
The same pattern applied to the payment step, where anonymity signals warrant a harder response.
Account Takeover
Why a new DeviceID and country on an established account is the shape a step-up ladder is built to catch.
Credential Stuffing
Scoring the surge of replayed logins that step-up authentication slows after a leaked password list circulates.