Skip to main content
Most logins to an account are routine. A few are not: a login arriving through Tor, an anti-detect browser, or a brand-new device in a new country for an existing account. This guide scores the login in real time and reads the account behind it, then your login flow walks a threshold ladder: who passes, who gets a second factor, and who gets your hardest verification path.

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 named risk signals behind it, and ties it to the account and the device. Three things drive the gate: the User HID (your hashed account id), the durable Device ID, and the login’s risk signals. The Device ID holds through cleared cookies, incognito and IP changes, so a familiar device stays familiar and a new one stands out even when someone resets everything visible in the browser. ShieldLabs returns the score and the signals on each identification and detects Account takeover on your users as a High-Risk Event, available in the analytics dashboard, the API and webhooks; you choose the action for each case (allow, require 2FA or hold for verification) in your login flow.

Gate the login on the Risk Score

The login policy: read the identification’s risk_score and the weight of each named risk signal, 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: a Device ID this User HID has never used, a country it has never signed in from, or an account with an Account takeover event is a stronger step-up trigger than the score alone. The outcome: a familiar device with a Trusted score passes untouched, while a Suspicious or Dangerous score, an unfamiliar device or an account with that event steps up.

Build it

1

Create a ShieldLabs account and get your keys

Start Free with 5,000 identifications, one time, no credit card, or log in. In the analytics dashboard, add the domain you want to protect under Integration > Domains, then open Integration > API keys and copy its keys with the copy button next to each. The Public Key loads the snippet in the browser. Keep the server credentials on your backend: the Private API Key reads the History API, and each webhook endpoint has its own whsec_… signing secret. See API keys and Integration.
2

Check the login as the form is filled in

Add the snippet to your login page and call forceCheckAnonymous for every login attempt, when the user starts filling the form (its first focus). It runs an identification every time, keeps the current Session ID and restarts the five-minute window, so you score the login as it is now; a plain checkAnonymous is skipped when the same browser was checked in the same visit within the last five minutes, and the login would reach your backend without a request ID. onInitialized fires when the check starts, before the snippet has sent the identification, so do not navigate away inside it: store the request ID and let the form submit normally. Pass the hashed User HID only after the password check succeeds, never a raw email or user id: forceCheckAuthenticatedUser on the first signed-in page, then checkAuthenticatedUser on every signed-in page. The account’s history then holds only its own signed-in activity; an attempt identified with the User HID before the password is checked would add the device of whoever typed the username to that account.
login.html
requestID from onInitialized is your join key to the webhook you receive server-side. Installing the snippet covers the framework versions of the same pattern.
3

Receive the webhook and cache it by request ID

ShieldLabs POSTs one webhook per 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, public_ip.country and user_hid you compare below all ride in data on the same webhook envelope. Delivery is at-most-once with no retries, so for a guaranteed read the helper falls back to the History API by request_id (read by user_hid to also pull the account’s recent identifications). History API reads and the webhook are free.
4

Walk your threshold ladder

Wait briefly for the Risk Score, then branch. The band is your starting point; the account and the signals array refine it. Below is a three-rung ladder you can tune to your own traffic. You choose the action for each rung (require 2FA, route to strong verification, hold and alert) and run it in your login flow; ShieldLabs returns the Risk Score and its named risk signals.
api/login.js
waitForScore polls the shared webhook cache, then falls back to a History API read by request_id and maps it to the webhook field names. accountView reads the account’s identifications by user_hid.
The Risk Score gauge at 70.00, Dangerous, and the Risk signals table of one identification in the analytics dashboard: Anti-detect Browser with weight 60 and Proxy with weight 10.The Risk Score gauge at 70.00, Dangerous, and the Risk signals table of one identification in the analytics dashboard in the dark theme: Anti-detect Browser with weight 60 and Proxy with weight 10.

The Risk Score of one identification and each risk signal with its weight, in the analytics dashboard.

5

Tune to your traffic

Start in a logging-only mode, watch how your real logins distribute across the bands, then raise friction where the data justifies it. A high-value account is a good place to draw the lines tighter.

The threshold ladder, band by band

The three bands and their ranges are defined once in Risk Scoring, and the cross-scenario action playbook lives in Acting on results. The API returns only the number (risk_score on the webhook, score in History), so map it to a band in your backend. Mapped to a login gate, a sensible starting ladder is: A high-value account is a good place to draw the lines tighter.

Signals worth weighting at login

The Risk Score already folds these in. To raise friction at a moderate score when a heavy signal is present, check the entry’s name slug or its weight. Each entry in signals is { name, weight } with a stable slug; the risk signals reference lists every slug with its weight. A slug can repeat with a partial weight when an earlier verdict is carried forward, so test for presence rather than counting entries. For quick boolean branching at the gate, the payload also carries a detection_flags object (detection_flags.tor, detection_flags.anti_detect_browser, detection_flags.browser_automation and so on), so you can branch without inspecting the signals array. Note that the anti-detect slug is antidetect_browser while its flag key is anti_detect_browser.
Pair the score with the account. Read the account’s recent identifications with a History API read by user_hid: a Device ID or country it has never used, or a worst band of Dangerous in its recent history, is a stronger step-up trigger than the same score on a familiar device. When an Account takeover event arrives for a user through the API or webhooks, or when you review it in the analytics dashboard, add the user to a watchlist and step up every login for that account until reviewed.
Pull an account's recent identifications
The response is a { data, total } envelope; data is an array of identifications (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). The shared shieldlabsHistory helper returns this data array for you. Comparing the current device and country with the account’s history is the check at login; across the account’s activity, ShieldLabs detects Account takeover as a High-Risk Event, available in the analytics dashboard, the API and webhooks. History API reads on account.shieldlabs.ai are free.

Honest caveat

Read a high Risk Score together with its named risk signals and the user’s history. A real customer on a corporate VPN can reach the Suspicious band; the signals show why, and you choose the action for each case. VPN and iCloud Private Relay add 15 each, so a real customer on a corporate VPN usually stays Trusted and reaches the Suspicious band only when another signal stacks on top. Requiring a second factor rather than a hard block on the upper rungs keeps those customers in while still slowing someone who only has the password.

Test it

Confirm the durable identity before you wire thresholds to real friction. Sign in once and note the device_id on the webhook, then reproduce the “new arrival” without a new device:
  • Clear cookies and storage, then sign in again. The cookie_id and visitor_id change, but the device_id stays the same.
  • Open an incognito or private window and sign in. The same device_id returns.
  • Switch networks (or turn on a VPN) so your IP changes, then sign in. The device_id holds; the signals array now also carries the masking signal (for example vpn), which raises the risk_score.
A different device, or a second browser on the same machine, returns a different device_id, which is the shape your ladder should step up on. This test proves a fresh cookie or a rotated IP cannot pass for a familiar device.

Next steps

Acting on results

The full per-band decision playbook, including signal-aware decisioning and how to combine the score with specific signals.

Risk signals

Every risk signal that can appear in signals, by slug, 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 approach applied to the payment step, where risk signals warrant a harder response.

Account Takeover

Why a new Device ID 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.