User-Initiated Multifactor Authentication

In this tutorial, we will show you how to implement and enable user-initiated multifactor authentication (MFA). We will cover how to:

  • Enable MFA using Auth0's Management Dashboard
  • Programmatically flag new users for MFA
  • Set the users up to initiate MFA enrollment when logging in for the first time.

Enable Multifactor Authentication

You can enable Multifactor Authentication (MFA) using the Dashboard.

Log in to your Auth0 account and navigate to the Multifactor Auth page of the Management Dashboard.

You'll have the option to enable MFA that uses either Push Notifications or SMS. You can also use both. Click the slider(s) next to the option(s) you want enabled.

Once you've enabled one or more types of MFA, you'll see the Customize MFA section, which is a text editor that allows you to write the code to determine when MFA is necessary. The code runs as part of your rules whenever a user logs in.

To help you get started, you'll see a template you can modify. Additional templates that implement various MFA features are available under Templates, which is located to the top right of the code editor.

As an example, the follow code snippet calls for MFA when:

  • The client specified is used
  • The user's app_metadata has a use_mfa flag set to true

Finally, if the two parameters above are met, MFA occurs every login.

function (user, context, callback) {

    // run only for the specified clients
    var CLIENTS_WITH_MFA = ['{REPLACE_WITH_YOUR_CLIENT_ID}'];
    
    if (CLIENTS_WITH_MFA.indexOf(context.clientID) !== -1) {
        if (user.app_metadata && user.app_metadata.use_mfa){

            context.multifactor = {
                // required
                provider: 'guardian', 

                // set to false to force Guardian authentication every login
                allowRememberBrowser: false
            };
        }
    }

    callback(null, user, context);
}

Flag New Users for MFA

In this step, we'll add functionality within the user creation/login process that flags users for MFA.

You'll need to get an access token to call the Management API during the user creation process. The only scope that you need to grant to the issued token is update:users_app_metadata.

Using this token, you can place a flag on app_metadata that indicates whether MFA is needed whenever that user logs in. More specifically, you'll be programmatically setting their the user's app_metadata field with useMfa = true.

You can do this by making the appropriate PATCH call to the Update a User endpoint of the Management API. Note that the body of the call omits most of the extra details (such as email and phone number) you might need to include.


curl --request POST \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN' \
  --data '{ "blocked": false, "email_verified": false, "email": "", "verify_email": false, "phone_number": "", "phone_verified": false, "verify_phone_number": false, "password": "", "verify_password": false,"user_metadata": {},"app_metadata": { "useMfa": true }, "connection": "", "username": "","client_id": "DaM8...rdyX"}'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID");
var request = new RestRequest(Method.POST);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
request.AddParameter("undefined", "{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID"

	payload := strings.NewReader("{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .body("{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID",
  "method": "POST",
  "headers": {
    "authorization": "Bearer MGMT_API_ACCESS_TOKEN"
  },
  "processData": false,
  "data": "{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}"
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require("request");

var options = { method: 'POST',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID',
  headers: { authorization: 'Bearer MGMT_API_ACCESS_TOKEN' },
  body: 
   { blocked: false,
     email_verified: false,
     email: '',
     verify_email: false,
     phone_number: '',
     phone_verified: false,
     verify_phone_number: false,
     password: '',
     verify_password: false,
     user_metadata: {},
     app_metadata: { useMfa: true },
     connection: '',
     username: '',
     client_id: 'DaM8...rdyX' },
  json: true };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };
NSDictionary *parameters = @{ @"blocked": @NO,
                              @"email_verified": @NO,
                              @"email": @"",
                              @"verify_email": @NO,
                              @"phone_number": @"",
                              @"phone_verified": @NO,
                              @"verify_phone_number": @NO,
                              @"password": @"",
                              @"verify_password": @NO,
                              @"user_metadata": @{  },
                              @"app_metadata": @{ @"useMfa": @YES },
                              @"connection": @"",
                              @"username": @"",
                              @"client_id": @"DaM8...rdyX" };

NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}"

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("POST", "/YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID", payload, headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(url)
request["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'
request.body = "{ \"blocked\": false, \"email_verified\": false, \"email\": \"\", \"verify_email\": false, \"phone_number\": \"\", \"phone_verified\": false, \"verify_phone_number\": false, \"password\": \"\", \"verify_password\": false,\"user_metadata\": {},\"app_metadata\": { \"useMfa\": true }, \"connection\": \"\", \"username\": \"\",\"client_id\": \"DaM8...rdyX\"}"

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]
let parameters = [
  "blocked": false,
  "email_verified": false,
  "email": "",
  "verify_email": false,
  "phone_number": "",
  "phone_verified": false,
  "verify_phone_number": false,
  "password": "",
  "verify_password": false,
  "user_metadata": [],
  "app_metadata": ["useMfa": true],
  "connection": "",
  "username": "",
  "client_id": "DaM8...rdyX"
]

let postData = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/users/USER_ID")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "POST"
request.allHTTPHeaderFields = headers
request.HTTPBody = postData

let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    println(error)
  } else {
    let httpResponse = response as? NSHTTPURLResponse
    println(httpResponse)
  }
})

dataTask.resume()

Initiate Guardian Enrollment

In this step, we'll initiate Guardian Enrollment for users who have been flagged for MFA. Note that you can use any MFA provider that integrates with Auth0 -- you do not necessarily have to use Guardian.

You'll need to get an access token to call the Management API. The only scope that you need to grant to the issued token is create:guardian_enrollment_tickets. You might consider adding both scopes to the access token when you make the initial request in the previous step in lieu of making two separate requests, each resulting in a token with a different scope.

You can enroll a user in Guardian MFA by making the appropriate POST call to the Create a Guardian Enrollment Ticket endpoint of the Management API.


curl --request POST \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket' \
  --header 'authorization: Bearer MGMT_API_ACCESS_TOKEN' \
  --data '{ "user_id": "", "email": "", "send_mail": false }'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket");
var request = new RestRequest(Method.POST);
request.AddHeader("authorization", "Bearer MGMT_API_ACCESS_TOKEN");
request.AddParameter("undefined", "{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io/ioutil"
)

func main() {

	url := "https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket"

	payload := strings.NewReader("{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("authorization", "Bearer MGMT_API_ACCESS_TOKEN")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket")
  .header("authorization", "Bearer MGMT_API_ACCESS_TOKEN")
  .body("{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket",
  "method": "POST",
  "headers": {
    "authorization": "Bearer MGMT_API_ACCESS_TOKEN"
  },
  "processData": false,
  "data": "{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }"
}

$.ajax(settings).done(function (response) {
  console.log(response);
});
var request = require("request");

var options = { method: 'POST',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket',
  headers: { authorization: 'Bearer MGMT_API_ACCESS_TOKEN' },
  body: { user_id: '', email: '', send_mail: false },
  json: true };

request(options, function (error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MGMT_API_ACCESS_TOKEN" };
NSDictionary *parameters = @{ @"user_id": @"",
                              @"email": @"",
                              @"send_mail": @NO };

NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
                                            completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                if (error) {
                                                    NSLog(@"%@", error);
                                                } else {
                                                    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
                                                    NSLog(@"%@", httpResponse);
                                                }
                                            }];
[dataTask resume];
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => "{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer MGMT_API_ACCESS_TOKEN"
  ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
  echo "cURL Error #:" . $err;
} else {
  echo $response;
}
import http.client

conn = http.client.HTTPSConnection("")

payload = "{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }"

headers = { 'authorization': "Bearer MGMT_API_ACCESS_TOKEN" }

conn.request("POST", "/YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket", payload, headers)

res = conn.getresponse()
data = res.read()

print(data.decode("utf-8"))
require 'uri'
require 'net/http'

url = URI("https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE

request = Net::HTTP::Post.new(url)
request["authorization"] = 'Bearer MGMT_API_ACCESS_TOKEN'
request.body = "{ \"user_id\": \"\", \"email\": \"\", \"send_mail\": false }"

response = http.request(request)
puts response.read_body
import Foundation

let headers = ["authorization": "Bearer MGMT_API_ACCESS_TOKEN"]
let parameters = [
  "user_id": "",
  "email": "",
  "send_mail": false
]

let postData = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: nil)

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/guardian/enrollments/ticket")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "POST"
request.allHTTPHeaderFields = headers
request.HTTPBody = postData

let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    println(error)
  } else {
    let httpResponse = response as? NSHTTPURLResponse
    println(httpResponse)
  }
})

dataTask.resume()