Skip to main content

Adaptive MFA and risk signals

Which signals we compute on every sign-in, how they add up to a risk level, what a step-up actually does, and how to see why one person was prompted.

Written for whoever runs IT10 min readUpdated

Adaptive multi-factor authentication means asking for a second factor when a sign-in looks unusual, and staying out of the way when it does not. The judgement is made from facts about the attempt — where it came from, whether the device has been seen before, how the account has been behaving — turned into a score, then a level, then a decision made by a policy rule you control.

This article covers the signals we compute, how they combine, what happens when a step-up fires, and — the part an administrator actually needs — how to find out afterwards why a particular person was prompted at a particular moment.

The order things happen in

Three stages run on every sign-in, in this order, and the order is not arbitrary.

  1. Network facts. The client address is resolved to network zones, and to a country, network operator and anonymising-service verdict where your organisation's IP intelligence can supply them. See Network zones.
  2. Behavioural risk. Those facts, plus what we know about this account's history, produce a set of signals with weights, a score, and a level of LOW, MEDIUM or HIGH. A row is written recording all of it.
  3. Policy. The ordered rules are evaluated against the user, their groups, the zone, the device and the risk level, and the first matching rule decides. The decision is written back onto the risk row, so the reason and the outcome live together rather than in two tables nobody joins.

Each stage's output is a stored, queryable record. That is a deliberate design choice: the question why did this sign-in behave this way? has to be answerable from a database afterwards, not from a log grep, or it will not be answered at all.

Every signal we compute

There are thirteen. Each is a named string, a weight, and a human-readable detail explaining what was observed. The names are stable, because they appear in stored records and in policy conditions.

The full signal set, with the default weight each contributes to the score. Weights are configurable per organisation; the values shown are the defaults.
SignalFires whenDefault weight
impossible_travelTwo sign-ins imply a speed above the configured limit — 900 km/h by default45
ip_reputationThe device has been marked compromised by endpoint telemetry40
anonymizing_proxyThe source is a Tor exit node, or a known VPN or anonymiser egress35
new_deviceThe device fingerprint has not been seen for this user before25
new_countryThe country is not in the account's known set20
recent_failed_loginsThree or more recent failed attempts on the account; half weight for one or two20
missing_user_agentThe request carried no device identity and no browser identification at all15
new_asnThe network operator is new for this account10
hosting_providerThe address belongs to a hosting provider rather than a consumer or corporate network10
off_hoursThe attempt is outside the configured business window — 07:00 to 20:00 UTC by default10
private_networkThe source is on a private network. This one subtracts ten−10
no_baselineThis is the first sign-in we have ever seen for the account. Informational only0
externalAnother module — endpoint detection, or log analysis — has asserted something about this user, device or addressset by the caller

A known device that you have explicitly marked as trusted contributes a small negative weight rather than nothing, so a familiar laptop actively pulls the score down.

Four details about how those signals behave matter more than the weights themselves:

  • A signal that contributes zero is not recorded at all. A zero-weight row in the interface reads as 'we checked and it was fine', which is the opposite of what it means. The one exception is no_baseline, which is recorded precisely because it explains an absence.
  • A brand-new account has its three 'new' signals suppressed. A first-ever sign-in has no device, country or network history, so all three would fire at once and every new joiner would be challenged on their first day. That teaches people to click through prompts, which is worse than not prompting.
  • Impossible travel is separate from a country change on purpose. It needs coordinates at both ends, ignores movement under 50 km as geolocation jitter, and floors the elapsed time at one minute. Conflating the two produces a false positive every time somebody crosses a border by train.
  • The device fingerprint is a fingerprint, not an identity. It is derived from the browser identification and a client-supplied device identifier, with version numbers stripped out so a routine browser update does not look like a brand-new machine. Its value is the negative case: a match is weak evidence, a mismatch is worth noticing.

How the signals become a level

The weights are summed and clamped between 0 and 100. The total maps to a level using two thresholds, which default to 30 for MEDIUM and 60 for HIGH. Policy rules can then say 'this level or above' rather than naming a number, which is what keeps a rule readable when you later retune the weights.

Where another module has asserted a minimum level — the endpoint sensor believing a device is compromised, for example — that floor can only raise the level, never lower it. A quiet score must not be able to launder a known compromise.

The tunable settings and their defaults. Every one is per organisation.
SettingDefaultWhat it changes
Medium threshold30The score at which a sign-in becomes MEDIUM
High threshold60The score at which it becomes HIGH
Velocity limit900 km/hThe speed above which travel is impossible
Business hours07:00–20:00 UTCThe window outside which off_hours fires. Setting start equal to end disables the check
Baseline window30 daysHow far back the known devices, countries and networks are drawn from
EnabledOnSwitching it off scores every sign-in as zero and LOW, which makes every risk-based rule inert

What a step-up actually does

When a rule answers STEP_UP, the sign-in does not fail. The password call succeeds and returns a challenge instead of a session: a short-lived token, the list of factors that would satisfy the rule, and — deliberately — the reason.

That challenge carries the risk level, the signal names that fired, the name of the rule that decided, a message written for the person reading it, and an identifier for the evaluation. The reasoning behind including all of that is worth quoting in spirit: a person prompted for a second factor on a sign-in that looked entirely ordinary to them will call you. Telling them 'we did not recognise this device' turns that call into a self-service answer.

One case to handle in your own head before it happens: if the challenge comes back with an empty factor list, the policy is demanding a factor that person has not enrolled. They should be sent to enrolment, not asked to try again. That is the shape of an administrator caught by a passkey rule before they have a passkey.

Two limits on that comfort, stated plainly. A step-up only helps where a second factor is enrolled, so the value of the whole mechanism is bounded by your enrolment coverage. And a rule requiring a phishing-resistant factor is applied when the sign-in is first answered but not re-checked when the code is submitted — see Multi-factor authentication factors for what that means in practice.

The record you can go back to

Every authentication writes a row, allowed or not. That is the point: an administrator asking 'why did Jane get prompted at 03:00 on Tuesday?' should get an answer from a query, not from an inference.

Each row holds the account and email, the address, the browser identification, the device fingerprint, the country and network operator, the score, the level, the matching network zones, the outcome, the policy and rule that decided, and the signal breakdown itself. The breakdown is an array, and each entry has exactly three fields:

A stored signal breakdown, as it appears on the risk event
[
  { "signal": "new_device",
    "weight": 25,
    "detail": "device fingerprint 9f2c...a41 not seen before for this user" },
  { "signal": "new_country",
    "weight": 20,
    "detail": "first sign-in from PL; previously seen from DE, AT" },
  { "signal": "off_hours",
    "weight": 10,
    "detail": "sign-in at 03:12 UTC, outside the configured 07:00-20:00 window" }
]

Fifty-five points, above the medium threshold and below the high one, so the level is MEDIUM and the rule that steps up on medium-or-above matched. The outcome recorded on the row is one of PENDING, ALLOW, STEP_UP or DENY.

The baseline of known devices, countries and networks is updated only after a sign-in fully succeeds, second factor included. Recording on the attempt would let somebody who already holds the password teach the baseline that their device and country are normal, so the second attempt would score clean. It is a small detail with a large consequence.

What you can look at and change

The risk surfaces are gated on two permissions — iam:risk.read to look and iam:risk.write to change — so you can give someone visibility into why people are being prompted without giving them the ability to retune the weights.

Risk events
The rows above, per sign-in, with their full signal breakdown and the policy outcome attached.
Devices
The known devices per user, with a first-seen and last-seen time, a count of sign-ins, a platform, a trusted flag you set, and a posture value of unknown, compliant, noncompliant or compromised.
External signals
Assertions raised by other modules against a user, a device or an address, each with a source, a reason, a score adjustment, an optional minimum level and an expiry.
Explain
Ask what a hypothetical sign-in would have scored, before you change a weight. Nothing is persisted and no policy is applied — it is the safe way to answer 'if I raise the new-device weight to 40, who starts getting prompted?'

Two validation rules will stop you making the settings incoherent: the high threshold cannot be below the medium one, and business hours must be values from 0 to 23. A window whose start is later than its end is read as wrapping past midnight, which is what a night shift needs.

Tuning it without training people to click through

The failure mode of any adaptive system is prompting so often that the prompt stops meaning anything. Three habits keep that from happening.

  • Mark your genuinely trusted devices as trusted. It is the only signal that subtracts, and it is the cheapest way to quieten the office.
  • Set business hours to your actual hours, or switch the check off. A firm with staff in three time zones gets nothing from a signal that fires on half of them every day.
  • Change one weight at a time, and use the explain surface first. Retuning several at once and watching the prompt rate move tells you nothing about which change did it.

When you are ready to decide what the levels should cause rather than what produces them, read Sign-on policies and the decision trace — the risk level is an input to a rule, and the rule is where the outcome is actually decided.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in identity and access