Replying to my own question with our workaround, in case it helps anyone else.
Here is our action:
exports.onExecutePostLogin = async (event, api) => {
api.samlResponse.setAttribute('mfa_passed', 'false')
const enrolledFactors = event.user.enrolledFactors;
if (enrolledFactors && enrolledFactors.length > 0) {
console.log('User Enrolled Factors:', JSON.stringify(enrolledFactors, null, 2));
const isOtpEnrolled = enrolledFactors.some(factor => factor.type === 'otp');
if (!isOtpEnrolled) {
api.multifactor.enable('any', { allowRememberBrowser: false });
api.transaction.setMetadata('mfa_complete', 'true');
return;
}
}
if (event.user.mfa_email === 'true')
{
api.prompt.render('ap_8cDH62MbNTAFGP2b5GpZLt');
if (event.prompt != undefined && event.prompt.vars != undefined)
{
api.samlResponse.setAttribute('mfa_passed', event.prompt.vars.mfa_complete)
console.log(event.prompt.vars.mfa_complete)
}
}
else{
api.multifactor.enable('any', { allowRememberBrowser: false });
}
}
/**
* @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.onContinuePostLogin = async (event, api) => {
if (event.user.mfa_email === 'true')
{
if (event.prompt != undefined && event.prompt.vars != undefined && event.prompt.vars.mfa_complete !== 'true' && event.transaction?.metadata?.mfa_complete !== 'true')
{
console.log(event.prompt.vars.mfa_complete);
api.multifactor.enable('any', { allowRememberBrowser: false });
}
else{
return;
}
}
}
and here is the exported json for our form which we started from the email otp template
{
"version": "4.0.0",
"form": {
"name": "Email OTP verification with Auth0 Email Provider",
"languages": {
"primary": "en"
},
"nodes": [
{
"id": "step_qJEB",
"type": "STEP",
"coordinates": {
"x": 599,
"y": -615
},
"alias": "Email verification",
"config": {
"components": [
{
"id": "rich_text_Cqas",
"category": "BLOCK",
"type": "RICH_TEXT",
"config": {
"content": "<h2>Check your email</h2><p>Please enter the 6-digit verification code that was sent to <strong>{{context.user.email}}</strong>. The code is valid for 5 minutes.</p>"
}
},
{
"id": "verification_code",
"category": "FIELD",
"type": "NUMBER",
"label": "Verification code",
"required": true,
"sensitive": false
},
{
"id": "next_button_BtEe",
"category": "BLOCK",
"type": "NEXT_BUTTON",
"config": {
"text": "Continue"
}
},
{
"id": "resend_button_7eih",
"category": "BLOCK",
"type": "RESEND_BUTTON",
"config": {
"active_text": "Didn't receive the code?",
"button_text": "Resend",
"waiting_text": "Resend in {{remaining_seconds}} seconds",
"text_alignment": "CENTER",
"flow_id": "#FLOW-1#",
"max_attempts": 3,
"waiting_time": 60
}
}
],
"next_node": "flow_hq3b"
}
},
{
"id": "flow_hq3b",
"type": "FLOW",
"coordinates": {
"x": 1202,
"y": -204
},
"config": {
"flow_id": "#FLOW-2#",
"next_node": "$ending"
}
},
{
"id": "flow_3Qle",
"type": "FLOW",
"coordinates": {
"x": 337,
"y": -50
},
"config": {
"flow_id": "#FLOW-1#",
"next_node": "step_qJEB"
}
},
{
"id": "step_5cJB",
"type": "STEP",
"coordinates": {
"x": 169,
"y": 558
},
"alias": "New step",
"config": {
"components": [
{
"id": "choice_GAHd",
"category": "FIELD",
"type": "CHOICE",
"label": "MFA Factor",
"required": false,
"sensitive": false,
"config": {
"multiple": false,
"options": [
{
"label": "OTP Authenticator",
"value": "otp"
},
{
"label": "Email",
"value": "email"
}
]
}
},
{
"id": "next_button_xA1f",
"category": "BLOCK",
"type": "NEXT_BUTTON",
"config": {
"text": "Continue"
}
}
],
"next_node": "router_5UHb"
}
},
{
"id": "router_5UHb",
"type": "ROUTER",
"coordinates": {
"x": 734,
"y": 346
},
"alias": "Choose MFA Factor",
"config": {
"rules": [
{
"id": "id_5853828829579",
"alias": "Email",
"condition": {
"operands": [
{
"operands": [
"{{fields.choice_GAHd}}",
"email"
],
"operator": "EQ"
}
],
"operator": "AND"
},
"next_node": "flow_3Qle"
},
{
"id": "id_9874204690555",
"alias": "Otp",
"condition": {
"operands": [
{
"operands": [
"{{fields.choice_GAHd}}",
"otp"
],
"operator": "EQ"
}
],
"operator": "AND"
},
"next_node": "flow_2V1u"
}
]
}
},
{
"id": "flow_2V1u",
"type": "FLOW",
"coordinates": {
"x": 1203,
"y": 255
},
"alias": "New flow",
"config": {
"flow_id": "#FLOW-3#",
"next_node": "$ending"
}
}
],
"start": {
"next_node": "step_5cJB",
"coordinates": {
"x": -197,
"y": 41
}
},
"ending": {
"resume_flow": true,
"coordinates": {
"x": 1709,
"y": -205
}
}
},
"flows": {
"#FLOW-1#": {
"name": "Generate OTP & Send email",
"actions": [
{
"id": "generate_otp",
"type": "OTP",
"action": "GENERATE_CODE",
"allow_failure": false,
"mask_output": false,
"params": {
"reference": "{{context.user.email}}",
"length": 6
}
},
{
"id": "send_email",
"type": "AUTH0",
"action": "SEND_EMAIL",
"allow_failure": false,
"mask_output": false,
"params": {
"to": "{{context.user.email}}",
"subject": "{{custom_vars.code}} is your verification code",
"body": "{% if user.nickname %}\n<p>Hello {{user.nickname}},</p>\n{% else %}\n<p>Hello,</p>\n{% endif %}\n<p>{{custom_vars.code}} is your verification code</p>",
"custom_vars": {
"code": "{{actions.generate_otp.code}}"
}
}
}
]
},
"#FLOW-2#": {
"name": "Verify OTP & Update user",
"actions": [
{
"id": "verify_otp",
"type": "OTP",
"action": "VERIFY_CODE",
"allow_failure": false,
"mask_output": false,
"params": {
"reference": "{{context.user.email}}",
"code": "{{fields.verification_code}}"
}
},
{
"id": "if_then_condition_mnXT",
"alias": "Is it valid?",
"type": "FLOW",
"action": "BOOLEAN_CONDITION",
"allow_failure": false,
"mask_output": false,
"params": {
"if": {
"operands": [
{
"operands": [
"{{actions.verify_otp.valid}}",
true
],
"operator": "EQ"
}
],
"operator": "AND"
},
"then": [
{
"id": "update_user_IPZP",
"type": "AUTH0",
"action": "UPDATE_USER",
"allow_failure": false,
"mask_output": false,
"params": {
"connection_id": "#CONN-1#",
"user_id": "{{context.user.user_id}}",
"changes": {
"email_verified": true
}
}
},
{
"id": "store_shared_variable_ggJG",
"alias": "mfa_complete",
"type": "FLOW",
"action": "STORE_VARS",
"allow_failure": false,
"mask_output": false,
"params": {
"vars": {
"mfa_complete": "true"
}
}
}
],
"else": [
{
"id": "show_error_message_Whan",
"type": "FLOW",
"action": "ERROR_MESSAGE",
"allow_failure": false,
"mask_output": false,
"params": {
"message": "We're sorry, your verification code is not valid. Please, review it and try it again."
}
}
]
}
}
]
},
"#FLOW-3#": {
"name": "check choice",
"actions": [
{
"id": "if_then_condition_alzN",
"alias": "check choice",
"type": "FLOW",
"action": "BOOLEAN_CONDITION",
"allow_failure": false,
"mask_output": false,
"params": {
"if": {
"operands": [
{
"operands": [
"{{fields.choice_GAHd}}",
"otp"
],
"operator": "EQ"
}
],
"operator": "AND"
},
"then": [
{
"id": "store_shared_variable_k9gD",
"alias": "mfa_complete",
"type": "FLOW",
"action": "STORE_VARS",
"allow_failure": false,
"mask_output": false,
"params": {
"vars": {
"mfa_complete": "false"
}
}
}
],
"else": [
{
"id": "store_shared_variable_wxGS",
"alias": "mfa_complete",
"type": "FLOW",
"action": "STORE_VARS",
"allow_failure": false,
"mask_output": false,
"params": {
"vars": {
"mfa_complete": "true"
}
}
}
]
}
}
]
}
},
"connections": {
"#CONN-1#": {
"id": "ac_eXPm5kRYH1GtndQA84jrET",
"app_id": "AUTH0",
"name": "REPLACE_WITH_M2M_CONNECTION"
}
}
}