Trusted devices
Requires a license with the
device-fingerprintfeature. See pricing.
This feature watches admin sign-in. It recognizes the device behind each login, scores how risky the login looks, and asks for a second factor when the score is high. It locks out repeated failed attempts, can refuse addresses a reputation provider flags, and keeps a login history admins can review. People mark the browsers they trust, and a trusted browser lowers the score.
How it works
Section titled “How it works”Every sign-in gets a score from 0 to 100 and a level:
| Level | Score | Effect |
|---|---|---|
low | 0 to 30 | Sign-in proceeds. |
medium | 31 to 60 | A second factor is required. |
high | 61 to 80 | A second factor is required. |
critical | 81 to 100 | A second factor is required, and the instance logs a warning. |
The score rises for a device the person has not used before, a run of recent failed attempts, many different addresses, a switch between IPv4 and IPv6, sign-in outside the person's three busiest hours or during the unusual-hours window, an address the reputation provider reports as a VPN, and when the person has no trusted device at all. A trusted device lowers it. A flagged address, a Tor exit node and an unreadable login history require a second factor whatever the score.
Turn it on
Section titled “Turn it on”- Install a license that includes
device-fingerprint. See licensing and tiers. - Behind a load balancer or reverse proxy, set
TRUSTED_PROXIESto its CIDR ranges. Without it every login appears to come from the proxy. - Restart the instance.
People manage their trusted browsers from the account menu in the admin console, under Trusted devices.
Try it
Section titled “Try it”You need a token in TOKEN. The
quickstart shows how to get one.
-
Trust the browser or machine you are calling from:
Terminal window curl -X POST http://localhost:3001/api/admin/devices/trust \-H "Authorization: Bearer $TOKEN" \-H "Content-Type: application/json" \-d '{"label": "Work laptop"}'{ "device_id": "4c80c9ea-ebbe-4074-9073-d7dae73f89e5", "label": "Work laptop" } -
List your trusted devices:
Terminal window curl http://localhost:3001/api/admin/devices \-H "Authorization: Bearer $TOKEN"{"data": [{ "id": "4c80c9ea-ebbe-4074-9073-d7dae73f89e5", "user_id": "3e7cc859-1207-4c7c-9af2-363fa5079e0b", "label": "Work laptop", "last_seen_at": "2026-10-01T09:30:00Z", "created_at": "2026-10-01T09:30:00Z" }],"limit": 50,"offset": 0,"total_count": 1} -
Read your own login history. Put your user id in
USER_ID:Terminal window curl "http://localhost:3001/api/admin/session-anomaly/history?user_id=$USER_ID&limit=20" \-H "Authorization: Bearer $TOKEN"{"entries": [{ "id": "e6d012b9-2064-4efc-a5c5-a852ba27bbe7", "user_id": "3e7cc859-1207-4c7c-9af2-363fa5079e0b","ip": "203.0.113.7", "user_agent": "Mozilla/5.0", "success": true,"timestamp": "2026-10-01T08:02:10Z", "risk_level": "low", "risk_score": 10,"risk_reason": "10 signals: [no trusted devices configured]" }],"limit": 20,"offset": 0} -
Forget the device again:
Terminal window curl -X DELETE http://localhost:3001/api/admin/devices/4c80c9ea-ebbe-4074-9073-d7dae73f89e5 \-H "Authorization: Bearer $TOKEN"The answer is
204.
Block brute force
Section titled “Block brute force”The guard covers every sign-in route: password login and token, MFA
verification, magic link, OAuth, SAML, WebAuthn and SCIM. It counts refused
attempts (401 and 403) per tenant, address and account. After
brute_force_max_attempts failures within brute_force_window_secs, that
pair is blocked for brute_force_block_secs:
{ "error": "too many failed attempts: account temporarily locked", "code": "BRUTE_FORCE_BLOCKED" }The status is 429.
Refuse flagged addresses
Section titled “Refuse flagged addresses”Set IPREP_PROVIDER=abuseipdb and IPREP_API_KEY to check each sign-in
address with AbuseIPDB. Results are cached for an hour. A flagged address is
refused with 403:
{ "error": "access denied: IP address has been flagged as abusive", "code": "IP_REPUTATION_BLOCKED" }abuseipdb without an API key leaves the gate off.
| Variable | What it does | Default |
|---|---|---|
IPREP_PROVIDER | abuseipdb or none. Any other value turns the gate off. | none |
IPREP_API_KEY | The provider's API key. | empty |
IPREP_BLOCK_ABUSE_CONFIDENCE | Block at or above this abuse confidence. | 90 |
IPREP_BLOCK_TOR | Block Tor exit nodes. false does not turn Tor blocking off. Use ip_rep.block_tor below for that. | on |
IPREP_BLOCK_VPN | Block commercial VPN addresses. | false |
IPREP_FAIL_CLOSED | Refuse the sign-in when the provider cannot be reached. | off, so the sign-in proceeds |
Tune the scoring
Section titled “Tune the scoring”Set DEVICE_FINGERPRINT_CONFIG to a JSON object with any of these keys.
Invalid JSON is logged and the defaults are kept.
DEVICE_FINGERPRINT_CONFIG='{"brute_force_max_attempts": 10, "ip_rep": {"block_tor": false}}'| Key | Meaning | Default |
|---|---|---|
unusual_hour_start, unusual_hour_end | Hours (UTC, 0 to 23) that count as unusual. | 0, 5 |
brute_force_max_attempts | Failures before a block. | 5 |
brute_force_window_secs | Window the failures are counted in. | 900 |
brute_force_block_secs | How long a block lasts. | 1800 |
brute_force_max_states | Address and account pairs tracked at once. | 10000 |
history_retention_days | Age at which the purge route deletes login history. | 90 |
fail_open | Let a login through when its history cannot be read. | false |
ip_rep.block_tor | Block Tor exit nodes. | true |
If you set LYEVE_PLUGINS, include device-fingerprint in it.
Review logins
Section titled “Review logins”An admin who is not a super admin can read only their own history and anomalies.
Device and review routes
| Method | Path | Who | Purpose |
|---|---|---|---|
GET | /api/admin/devices | signed in | Your trusted devices (limit default 50, at most 500, offset). |
POST | /api/admin/devices/trust | signed in | Trust the device making this request. Optional body {"label": "Work laptop"}. |
DELETE | /api/admin/devices/{id} | signed in | Forget a device. 204. |
POST | /api/admin/devices/{id}/untrust | signed in | Same as the DELETE route. |
GET | /api/admin/session-anomaly/history?user_id=<id> | admin | A person's login history, with limit and offset. |
GET | /api/admin/session-anomaly/anomalies?user_id=<id> | admin | Risky logins and device changes found in that history. |
DELETE | /api/admin/session-anomaly/history | super admin | Delete the tenant's history older than history_retention_days. |
GET | /api/admin/session-anomaly/bruteforce/blocks | super admin | Active brute-force blocks in the tenant. |
DELETE | /api/admin/session-anomaly/bruteforce/blocks | super admin | Lift a block. Body {"ip": "203.0.113.7", "user_id": "<id>"}. See the caution above. |
GET | /api/admin/session-anomaly/iprep/cache | admin | Whether the reputation gate started, its provider and cache size. With IPREP_PROVIDER=none it reports "enabled": true and checks nothing. |
POST | /api/admin/session-anomaly/iprep/cache/clear | super admin | Clear the reputation cache. |
Nobody can read or change another person's devices.
With flows, the device_fingerprint.risk node scores a login from
a trigger's payload, so a flow can alert or lock an account on the result. A
privacy erasure request removes the addresses, browsers and device
fingerprints stored for the person.
Errors
Section titled “Errors”| Status | Message | Cause |
|---|---|---|
429 | BRUTE_FORCE_BLOCKED | Too many failed sign-ins. |
403 | IP_REPUTATION_BLOCKED | The sign-in address is flagged. |
404 | The requested endpoint does not exist. | The instance started without a license that includes device-fingerprint. |
402 | payment_required | The license stopped including device-fingerprint while the instance was running. |
Every other error
| Status | Message | Cause |
|---|---|---|
400 | user_id query parameter is required | History or anomalies without user_id. |
400 | invalid user_id format | user_id is not a UUID. |
400 | ip and user_id are required | Unblock without both. |
401 | unauthorized | A device route without a signed-in user. |
403 | access denied: admins can only view their own login history | An admin named another person's history. |
403 | access denied: admins can only view their own anomalies | An admin named another person's anomalies. |
404 | no block found for this ip+user pair | Nothing to unblock. |
404 | IP reputation not enabled | Cache clear while the gate could not start. |
Related
Section titled “Related”- Multi-factor authentication: the second factor a risky sign-in asks for.
- Captcha: a challenge after failed sign-ins.
- Rate limiting: limits on request volume.
- Harden your instance.