Customize Adaptive MFA
You can customize Adaptive MFA for a variety of scenarios with Auth0 Actions.
When to customize Adaptive MFA
If your users are not enrolled in MFA, you should use the default policy for Adaptive MFA. If a user is not enrolled in MFA and your Action assesses a high risk, you have limited options to stop a bad actor.
Before you begin to customize Adaptive MFA, ask yourself a few questions:
At what confidence level do you want to trigger MFA?
How do you want to measure risk?
Do you want Auth0 to measure confidence or do you want a custom measurement?
How will you handle users who are not enrolled in MFA?
Evaluate confidence
Adaptive MFA calculates an overall confidence score based on the analysis of three assessments. Each assessment has its own confidence score. To learn more, read Adaptive MFA.
Confidence scores
Confidence scores and their associated actions are described below:
Confidence score | Description | Action |
---|---|---|
low |
Login transaction does not match patterns previously displayed by user. | Require MFA. |
medium |
Login transaction somewhat matches patterns previously displayed by user. | Do not require MFA. |
high |
Login transaction closely matches patterns previously displayed by user. | Do not require MFA. |
neutral |
N/A. Reserved for future use. | N/A. Reserved for future use. |
Custom confidence scoring
If you want to implement your own method for evaluating the overall confidence score of different scenarios, you can use the data available in the riskAssessment object.
Read the examples below to learn how Adaptive MFA would score the confidence of different use cases.
Examples of high-risk, low-confidence scenarios
The following table describes high-risk scenarios that result in a low
confidence score:
User State | Desired Login Friction | Desired Enrollment Policy | Implementation |
---|---|---|---|
Enrolled in MFA | Do not require MFA | N/A (user already enrolled) | Use an Action to bypass MFA |
Not enrolled in MFA | Require email verification | Skip enrollment (do not collect additional authenticators) | Default behavior (no MFA-related Action) |
Not enrolled in MFA | Require email verification | Require MFA enrollment (collect additional authenticator) | Use an Action to force MFA enrollment (template available) |
Examples of low-risk, high-confidence scenarios
The following table describes low-risk scenarios that result in a high
confidence score:
User State | Desired Login Friction | Desired Enrollment Policy | Implementation |
---|---|---|---|
Enrolled in MFA | No friction | N/A (user already enrolled) | Default behavior (no MFA-related Action) |
Not enrolled in MFA | No friction | Skip enrollment (do not collect additional authenticators) | Default behavior (no MFA-related Action) |
Not enrolled in MFA | No friction | Require MFA enrollment (collect additional authenticator) | Use an Action to force MFA enrollment (template available) |
riskAssessment object
The riskAssessment
object contains the overall confidence score, versioning information, and details of the individual assessments.
Property | Description | Type | Possible values |
---|---|---|---|
confidence |
Overall confidence score calculated by Adaptive MFA. | string | low , medium , high , neutral |
version |
Version identifier of risk assessment API. | string | 1 |
assessments |
Object containing individual assessment details. | object | Read assessments object |
exports.onExecutePostLogin = async (event, api) => {
if (event.authentication && event.authentication.riskAssessment) {
event.authentication.riskAssessment = {
confidence: 'low' | 'medium' | 'high' | 'neutral',
version: '1',
assessments: {
UntrustedIP: {
confidence: 'low' | 'medium' | 'high' | 'neutral',
code: 'not_found_on_deny_list' | 'found_on_deny_list',
details: { // only if 'found_on_deny_list'
ip: '192.168.1.1',
matches: '192.168.0/64',
source: 'firehol',
category: 'abuse'
}
},
NewDevice: {
confidence: 'low' | 'medium' | 'high' | 'neutral',
code: 'match' | 'partial_match' | 'no_match',
details: {
device: 'known' | 'unknown',
useragent: 'known' | 'unknown',
}
},
ImpossibleTravel: {
confidence: 'low' | 'medium' | 'high' | 'neutral',
code: 'missing_geoip', | 'anonymous_proxy' | 'unknown_location' | 'initial_login' | 'location_history_not_found' | 'invalid_travel' | 'minimal_travel_from_last_login' | 'impossible_travel_from_last_login' | 'substantial_travel_from_last_login' | 'travel_from_last_login'
}
},
PhoneNumber: {
code: "requires_verification | ok",
confidence: "low | medium | high | neutral",
details: {
lineType: "FIXED_LINE | MOBILE | FIXED_LINE_OR_MOBILE | TOLL_FREE | PREMIUM_RATE | SHARED_COST | VOIP | PERSONAL_NUMBER | PAGER | UAN | UNKNOWN"
isValid: true | false,
countryCode: 1,
number: "+12223334444"
}
}
};
}
}
Was this helpful?
assessments object
The assessments
object contains details of the three individual risk assessments:
Each assessment includes a confidence score, a code that describes the evaluation result, and additional contextual information.
NewDevice assessment
The NewDevice
assessment determines if the user is logging in from a known device and contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
confidence |
Confidence score calculated by Adaptive MFA. | string | low , medium , high , neutral |
code |
Evaluation result of the assessment. | string | match , partial_match , no_match , initial_login , unknown_device , no_device_history , assessment_not_available |
details |
Additional contextual information. | object | Refer to table below. |
NewDevice assessment code property
The NewDevice
assessment code
property equals one of the following values:
Value | Description |
---|---|
match |
The property values of the details object are equivalent. |
partial_match |
The property values of the details object are similar. |
no_match |
The property values of the details object are different. |
initial_login |
The user logged in for the first time on the device. |
unknown_device |
Auth0 was unable to attain metadata for the device. |
no_device_history |
There is no login history associated with the device. |
assessment_not_available |
Auth0 could not perform an assessment of the device. |
NewDevice assessment details object
If the code
property value equals match
, partial_match
, or no_match
, the NewDevice
assessment contains the details
object with the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
device |
Device of the user. | string | known , unknown |
useragent |
User agent of the user. | string | known , unknown |
ImpossibleTravel assessment
The ImpossibleTravel
assessment determines if the user is logging in from a location that would indicate impossible travel and contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
confidence |
Confidence score calculated by Adaptive MFA. | string | low , medium , high , neutral |
code |
Evaluation result of the assessment. | string | minimal_travel_from_last_login , travel_from_last_login , substantial_travel_from_last_login , impossible_travel_from_last_login , invalid_travel , missing_geoip , anonymous_proxy , unknown_location , initial_login , location_history_not_found , assessment_not_available |
UntrustedIP assessment
The UntrustedIP
assessment determines if the user’s IP address is present in Auth0’s repository of low-reputation IP addresses (“deny list”) and contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
confidence |
Confidence score calculated by Adaptive MFA. | string | low , medium , high , neutral |
code |
Evaluation result of the assessment. | string | not_found_on_deny_list , found_on_deny_list , invalid_ip_address , assessment_not_available |
details |
Additional contextual information. | object | Refer to table below. |
UntrustedIP assessment details object
If the UntrustedIP
assessment code
property value equals found_on_deny_list
, the details
object is present and contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
ip |
IP address of the device. | string | Any valid IPv4 or IPv6 address. |
matches |
Subnet mask that IP address belongs to. | string | Any valid IPv4 or IPv6 subnet mask. |
source |
Name of threat intelligence source for the deny list. | string | Any valid text. |
category |
Category indicating why IP address is untrusted. | string | abuse , anonymizer , datacenter , reputation , unroutable |
UntrustedIP assessment details object category property
The UntrustedIP
assessment details
object category
property describes the general reason why Adaptive MFA considers a given IP address untrusted and equals one of the following values:
Value | Description |
---|---|
abuse |
IP address exhibited abusive behaviors or was found to be member of bot nets. |
anonymizer |
IP address belongs to anonymizing services such as VPN providers, open proxies, and TOR exit nodes. |
datacenter |
IP address belongs to cloud hosting providers and colocation datacenters. |
reputation |
IP address has a poor reputation score based on activity. |
unroutable |
IP address is not in any range allocated or delegated by any authorized Internet registry or allowed for public use. |
PhoneNumber assessment
The PhoneNumber
assessment assesses the risk of a phone number for an incoming transaction and contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
code |
Describes the evaluation result. | string | ok , requires_verification , phone_number_not_provided , assessment_not_available |
confidence |
Confidence score calculated by Adaptive MFA. | string | low , medium , high , neutral |
details |
Additional contextual information. | object | Refer to table below. |
PhoneNumber assessment details object
The PhoneNumber
assessment details
object contains the following properties:
Property | Description | Type | Possible values |
---|---|---|---|
lineType |
Type of phone line | string | FIXED_LINE , MOBILE , FIXED_LINE_OR_MOBILE , TOLL_FREE , PREMIUM_RATE , SHARED_COST , VOIP , PERSONAL_NUMBER , PAGER , UAN , UNKNOWN |
isValid |
Returns the validity of the number | boolean | true , false |
countryCode |
Country code of the phone origin | integer | 0-999 |
number |
Phone number | string | Valid number including countryCode |
Action result outcomes
If any of your Actions trigger MFA based on confidence score, the default Adaptive MFA policy triggers MFA when the confidence score is low
.
The following table shows the possible outcomes based on the combination of Actions and default Adaptive MFA policy actions.
Action result | Adaptive MFA action | Outcome |
---|---|---|
Unauthorized | Trigger MFA | Unauthorized |
Unauthorized | No MFA Required | Unauthorized |
Trigger MFA | Trigger MFA | Trigger MFA |
Trigger MFA | No MFA Required | Trigger MFA |
No MFA Required | Trigger MFA | Trigger MFA |
No MFA Required | No MFA Required | No MFA Required |
Action templates
Auth0 provides two Action templates based on Adaptive MFA for you to customize: Adaptive MFA and Require MFA Enrollment.
Adaptive MFA template
This template provides an example and starting point for how to build a custom business flow using individual risk assessments.
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
if (event.authentication &&
event.authentication.riskAssessment &&
event.authentication.riskAssessment.assessments.NewDevice) {
// Example condition: prompt MFA only based on the NewDevice
// confidence level, this will prompt for MFA when a user is logging in
// from an unknown device.
let shouldPromptMfa;
switch (event.authentication.riskAssessment.assessments.NewDevice.confidence) {
case 'low':
case 'medium':
shouldPromptMfa = true;
break;
case 'high':
shouldPromptMfa = false;
break;
case 'neutral':
// When this assessor has no useful information about the confidence,
// do not prompt MFA.
shouldPromptMfa = false;
break;
}
// It only makes sense to prompt for MFA when the user has at least one
// enrolled MFA factor.
const canPromptMfa = event.user.multifactor && event.user.multifactor.length > 0;
if (shouldPromptMfa && canPromptMfa) {
api.multifactor.enable('any', { allowRememberBrowser: true });
}
}
};
Was this helpful?
Require MFA Enrollment template
This template demonstrates how you could enforce MFA enrollment when using a standard or Adaptive MFA policy. It uses event.user.multifactor
to check if the user is enrolled in MFA, and if they’re not, prompts for enrollment.
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
if (!event.user.multifactor || event.user.multifactor.length == 0) {
api.multifactor.enable('any', { allowRememberBrowser: true });
}
};
Was this helpful?
Action use cases
Here are some suggestions for how to build custom Actions based on your use case.
Perform an action if overall confidence score is X
Assess the riskAssessment.confidence
property, and then compare it with the constants high
, medium
, or low
:
exports.onExecutePostLogin = async (event, api) => {
const { riskAssessment } = event.authentication || {};
const riskIsMedium = riskAssessment && riskAssessment.confidence === 'medium';
if (riskIsMedium) {
// ....
}
}
Was this helpful?
Perform an action if confidence score is above or below X
Confidence scores are discrete values—not in a range—so you cannot use comparison operators (such as <
or >
) to evaluate multiple values in a single condition.
Use multiple conditions to logically combine all the confidence scores you want to handle. For example, if you want to know when the confidence score is greater than low
, check if it’s equal to medium
or high
:
exports.onExecutePostLogin = async (event, api) => {
const { riskAssessment } = event.authentication || {};
const riskIsMediumOrHigh = riskAssessment &&
(riskAssessment.confidence === 'high' ||
riskAssessment.confidence === 'medium');
if (riskIsMediumOrHigh) {
// ...
}
}
Was this helpful?
Get additional details if overall confidence score is X
The riskAssessment
object is saved in your tenant logs. You can view log entries to see the risk assessment score and the determining factors (reasons).
You can view the riskAssessment
object and report the results elsewhere. For example, you can send an email or save a record in an external database.
exports.onExecutePostLogin = async (event, api) => {
const { riskAssessment } = event.authentication || {};
const riskIsLow = riskAssessment && riskAssessment.confidence === 'low';
if (riskIsLow) {
// log(externalDatabase, riskAssessment);
}
}
Was this helpful?
Perform an action if a specific assessment has a specific result
Use the assessments object to access the details for individual assessments, including the code
property:
exports.onExecutePostLogin = async (event, api) => {
const { riskAssessment } = event.authentication || {};
const { ImpossibleTravel } = riskAssessment && riskAssessment.assessments;
if (ImpossibleTravel.code === 'impossible_travel_from_last_login') {
// ...
}
}
Was this helpful?
Aggregate assessments for a custom overall confidence score.
Use the assessments object to access the details for individual assessments, and then use the confidence
property, the code
property, or both.
To learn more about custom confidence scoring, read Custom confidence scoring.
Block current transaction and return error and message if a specific assessment has a specific result
Use the assessments object to access the details for individual assessments, including the code
property.
Block the login transaction from completing by returning the callback function with an UnauthorizedError
object as the error parameter. The UnauthorizedError
object always sets error
to unauthorized
, but you can customize the error_message
:
exports.onExecutePostLogin = async (event, api) => {
const { riskAssessment } = event.authentication || {};
const { ImpossibleTravel } = riskAssessment && riskAssessment.assessments;
if (ImpossibleTravel.code === 'impossible_travel_from_last_login') {
return api.access.deny('Login blocked due to impossible travel detected.')
}
}
Was this helpful?
This redirects the user back to the application's callback URL with the error
and error_message
parameters included.
Safely handle when Auth0 fails to execute assessments
Auth0 automatically assigns a low
confidence score if there is any sort of failure performing the risk assessment.
To mitigate this scenario, use the assessments object to inspect the code
property for each individual assessment and check if the value is set to assessment_not_available
.