Docs/Account/Guides/Risk Engine

Risk Engine

Vyntech Account's behavioral risk engine evaluates every login attempt in real time, computing a risk score based on device, network, geographic, and behavioral signals. The score determines whether the login is allowed immediately, challenged with step-up verification, or blocked entirely. This guide explains how scoring works, what factors contribute, and how to configure the engine for your security posture.

How It Works

Every time a user attempts to log in, the risk engine runs before tokens are issued. It collects contextual signals — the device fingerprint, IP address, geographic location, login velocity, and historical behavior — then computes a cumulative risk score from 0 to 100.

The score is compared against two configurable thresholds set in your tenant settings:

Allow

Score < challenge_threshold

Tokens issued immediately

Challenge

Score ≥ challenge & < block

Step-up verification required

Block

Score ≥ block_threshold

Login denied, admin notified

The risk score, contributing factors, and decision are recorded in the audit log for every login attempt — whether it succeeds or not. You can also subscribe to risk events via webhooks for real-time monitoring.

Risk Factors

The risk score is the sum of weighted factors that fire during evaluation. Multiple factors can contribute to the same login attempt. Here's the full breakdown:

  • New device (device fingerprint not seen before for this user): +20–30 — The fingerprint is derived from user-agent, screen resolution, timezone, and installed fonts. A completely new device scores higher than a partially recognized one.
  • New IP address (first login from this IP): +10–20 — IPs the user has logged in from within the last 30 days are considered "known." A brand-new IP scores 20; a new IP from a known ISP/ASN scores 10.
  • New geographic location (different city/country): +15–25 — GeoIP lookup determines the city and country. A new city in a known country scores 15; a completely new country scores 25.
  • Impossible travel (two logins from distant locations in impossible timeframe): +40–50 — If the user logged in from Sydney 30 minutes ago and now attempts from London, the travel speed exceeds what's physically possible. This is the highest-weighted single factor.
  • Login velocity (multiple failed attempts recently): +15–30 — More than 3 failed attempts in the last 10 minutes scores 15; more than 8 scores 30. This overlaps with but is separate from account lockout.
  • Tor/VPN exit node detected: +20–35 — Known Tor exit nodes score 35; commercial VPN endpoints score 20. The list is updated daily from public threat intelligence feeds.
  • Time-of-day anomaly (login at unusual hour for this user): +5–15 — The engine builds a per-user activity profile. A login at 3 AM when the user typically logs in between 9 AM–6 PM scores higher. This factor has the lowest weight since legitimate travel can cause it.

Score capping: The total score is capped at 100. Factor weights are additive — a login from a new device on a Tor exit node from a new country could score 20 + 35 + 25 = 80, triggering a challenge even without impossible travel.

Decision Outcomes

Based on the computed score and your tenant's configured thresholds, the engine makes one of three decisions:

Allow (score < challenge_threshold)

The login proceeds normally. Access and refresh tokens are issued. The risk score is still recorded in the audit log for visibility. Default threshold: 70.

Challenge (score ≥ challenge_threshold and < block_threshold)

The login response returns challenge_required: true along with a challenge_tokenand the challenge type (email OTP or forced MFA). Your app must complete the challenge before tokens are issued. The user's email is notified of the suspicious login attempt.

Block (score ≥ block_threshold)

The login is denied outright. The response returns a 403 with an error message. The tenant admin is notified via webhook (if configured). The user receives an email warning about the blocked attempt. Default threshold: 90.

Login response branching: Your frontend should handle all three outcomes from POST /auth/login: direct success (tokens returned), MFA required, challenge required, or error (blocked). See the Authentication Flows page for the complete decision tree.

Configuring Thresholds

Thresholds are configured per-tenant via the Settings API. Lower thresholds are more aggressive (trigger challenges/blocks more often). The example below tunes the engine to be more aggressive — challenging at score 50 (default 70) and blocking at 75 (default 90).

PATCH/api/v1/settings🔒 Auth

Update risk engine thresholds and configuration.

curl -X PATCH https://id.vyntech.com.au/api/v1/settings \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..." \ -H "Content-Type: application/json" \ -d '{ "risk_engine": { "enabled": true, "challenge_threshold": 50, "block_threshold": 75, "challenge_type": "email_otp", "trust_device_days": 30 } }'
Request Body
{
  "risk_engine": {
    "enabled": true,
    "challenge_threshold": 50,
    "block_threshold": 75,
    "challenge_type": "email_otp",
    "trust_device_days": 30
  }
}

Validation rules: challenge_threshold must be less than block_threshold. Both must be between 1 and 99. Setting enabled: false disables the risk engine entirely — all logins will be allowed (still subject to password policy and MFA).

Challenge Types

When the risk score triggers a challenge, the type of verification required depends on your challenge_type setting:

email_otp — Email One-Time Password

A 6-digit OTP is sent to the user's registered email address. The code expires after 10 minutes. The user submits it via POST /auth/verify-challenge. This is the default and recommended challenge type — it works for all users regardless of whether they have MFA set up.

mfa_required — Force TOTP Verification

Forces the user to provide a TOTP code from their authenticator app, even if MFA is not normally required for their account. If the user doesn't have MFA set up, the system falls back to email_otp automatically. Best for tenants where most users already have MFA enabled.

block — Deny Entirely

Instead of issuing a challenge, any score above challenge_thresholdresults in an immediate block. Use this for high-security environments where you'd rather deny access than risk a compromised challenge verification. The user must contact an admin to proceed.

Device Trust

Once a device passes a risk challenge, it can be "trusted" for a configurable number of days. During this trust period, the device's fingerprint is recognized and won't trigger the new_device risk factor or require additional challenges — even if other low-weight factors fire.

The trust_device_days setting controls how long device trust lasts. Setting it to 0 disables device trust entirely (every login is evaluated fresh).

PATCH/api/v1/settings🔒 Auth

Configure device trust duration.

curl -X PATCH https://id.vyntech.com.au/api/v1/settings \ -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..." \ -H "Content-Type: application/json" \ -d '{"risk_engine": {"trust_device_days": 60}}'
Request Body
{
  "risk_engine": {
    "trust_device_days": 60
  }
}

How trust works internally: When a user passes a challenge, a trust record is stored linking the device fingerprint + user ID with an expiration timestamp. On subsequent logins from the same device, the new_device factor is suppressed and the overall score is reduced by the device trust bonus (typically -20).

Monitoring Risk Events

You can subscribe to risk events via webhooks to get real-time notifications when high-risk logins occur. The login.risk_challenge and login.risk_blocked events fire whenever the engine triggers a challenge or block decision.

Example Webhook Payload

login.risk_blocked event
{
  "event": "login.risk_blocked",
  "timestamp": "2024-11-15T03:22:41Z",
  "tenant_id": "tnt_01H7ABCD5E6F7G8H9I0J",
  "data": {
    "user_id": "usr_01H8KXYZ4F2B7NQ9RPWT3M6J",
    "email": "jane@acme-corp.com",
    "risk_score": 92,
    "factors": [
      {"name": "impossible_travel", "weight": 45, "detail": "Sydney → London in 28min"},
      {"name": "new_device", "weight": 25, "detail": "Unknown fingerprint"},
      {"name": "tor_exit_node", "weight": 22, "detail": "IP 185.220.101.x"}
    ],
    "decision": "block",
    "ip_address": "185.220.101.42",
    "geo": {"city": "London", "country": "GB"},
    "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
    "previous_login": {
      "timestamp": "2024-11-15T02:54:12Z",
      "ip_address": "203.45.67.89",
      "geo": {"city": "Sydney", "country": "AU"}
    }
  }
}

Subscribe to these events by configuring your webhook URL in the tenant settings (see the Webhooks guide). Common patterns include piping blocked events into Slack, PagerDuty, or your SIEM.

Alert on blocks: Blocked logins often indicate an active attack against one of your users. Set up immediate notifications for login.risk_blockedevents so your security team can investigate — the user's credentials may be compromised.

Best Practices

Start with higher thresholds, then lower gradually

Begin with the defaults (challenge: 70, block: 90) and monitor your audit logs for 2–4 weeks. If you see few false positives, lower the challenge threshold by 10. Repeat until you find the right balance between security and user friction.

Monitor false positives via audit logs

Check the audit log for login.risk_challengeevents where the user successfully completed the challenge — these are legitimate logins that were flagged. If the rate is too high (>10% of logins), your thresholds are too aggressive.

Combine with MFA enforcement for defense-in-depth

The risk engine and MFA are complementary layers. MFA protects against stolen credentials; the risk engine detects anomalous behavior even after valid authentication. Together they provide significantly stronger security than either alone.

Trust devices for reasonable periods to avoid challenge fatigue

A trust_device_days of 30–90 days works well for most organizations. Too short and users face challenges every few days on familiar devices; too long and device compromise goes undetected longer.

Alert your security team on blocks

Set up webhook notifications for login.risk_blocked events and route them to your security operations channel. A blocked login is a strong signal of an active attack — investigate promptly and consider forcing a password reset for the targeted user.

What's Next

We use cookies and similar technologies to measure traffic and improve the site. You can choose which categories to allow. Manage Preferences.