Manage Authenticator Factors with Auth0 MFA API

Auth0 provides several API endpoints to help you manage the authenticators you're using with an application for multi-factor authentication (MFA). You can use these endpoints to build a complete user interface for letting users manage their authenticator factors.

Prerequisites

Before you can use the MFA APIs, you'll need to enable the MFA grant type for your application. Go to Auth0 Dashboard > Applications > Advanced Settings > Grant Types and select MFA.

Get MFA API access tokens

To call the MFA API to manage enrollments, you first need to obtain an access token for the MFA API.

To use the MFA API as part of an authentication flow, you can follow the steps detailed in Authenticate With Resource Owner Password Grant and MFA. If you are building a user interface to manage authentication factors, you'll need to obtain a token you can use for the MFA API at any moment, not only during authentication.

Universal Login

If you are using Universal Login, redirect to the Authorize endpoint, specifying the https://YOUR_DOMAIN/mfa/ audience, before using calling the MFA API.

Resource owner password grant

If you are using the Resource Owner Password Grant (ROPG), you have 3 options:

  • Ask for the https://YOUR_DOMAIN/mfa/ audience when logging-in, and use a refresh token to refresh it later.

  • If you need to list and delete authenticators, ask the user to authenticate again with /oauth/token, specifying the https://YOUR_DOMAIN/mfa/ audience. Users will need to complete MFA before being able to list and/or delete authentication factors.

  • If you only need to list authenticators, ask the user to authenticate again using /oauth/token with username/password. The endpoint will return an mfa_required error, and an mfa_token you can use to list authenticators. Users will need to provide their password to see their authenticators.

Scopes

When you request a token for the MFA audience, you can request the following scopes:

Scope Description
enroll To enroll a new authenticator.
read:authenticators To list existing authenticators.
remove:authenticators To delete an authenticator.

List authenticators

To get a list of the authenticators for a user, you can call the MFA Authenticators endpoint:


curl --request GET \
  --url 'https://YOUR_DOMAIN/mfa/authenticators' \
  --header 'authorization: Bearer MFA_TOKEN'
var client = new RestClient("https://YOUR_DOMAIN/mfa/authenticators");
var request = new RestRequest(Method.GET);
request.AddHeader("authorization", "Bearer MFA_TOKEN");
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_DOMAIN/mfa/authenticators"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("authorization", "Bearer MFA_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.get("https://YOUR_DOMAIN/mfa/authenticators")
  .header("authorization", "Bearer MFA_TOKEN")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'GET',
  url: 'https://YOUR_DOMAIN/mfa/authenticators',
  headers: {authorization: 'Bearer MFA_TOKEN'}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MFA_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/mfa/authenticators"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"GET"];
[request setAllHTTPHeaderFields:headers];

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, [
  CURLOPT_URL => "https://YOUR_DOMAIN/mfa/authenticators",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer MFA_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("")

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

conn.request("GET", "/YOUR_DOMAIN/mfa/authenticators", headers=headers)

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

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

url = URI("https://YOUR_DOMAIN/mfa/authenticators")

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

request = Net::HTTP::Get.new(url)
request["authorization"] = 'Bearer MFA_TOKEN'

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

let headers = ["authorization": "Bearer MFA_TOKEN"]

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/mfa/authenticators")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

You should receive information about the authenticator type(s) in the response:

[
  {
    "authenticator_type": "recovery-code",
    "id": "recovery-code|dev_IsBj5j3H12VAdOIj",
    "active": true
  },
  {
    "authenticator_type": "otp",
    "id": "totp|dev_nELLU4PFUiTW6iWs",
    "active": true,
  },
  {
    "authenticator_type": "oob",
    "oob_channel": "sms",
    "id": "sms|dev_sEe99pcpN0xp0yOO",
    "name": "+1123XXXXX",
    "active": true
  }
]


For the purposes of building a user interface for end users to manage their factors, you should ignore authenticators that have active as false. Those authenticators are not confirmed by users, so they can't be used to challenge for MFA.

The MFA API will list the following enrollments depending on the authenticator type:

Authenticator Actions
Push and OTP If push is enabled, Auth0 also creates an OTP enrollment. You will see both when listing enrollments.
SMS and Voice If both SMS and voice are enabled, when a user enrolls with either SMS or voice, Auth0 automatically creates two authenticators for the phone number, one for SMS and another for voice.
Email All verified emails will be listed as authenticators.

Enroll authenticators

See the following links for details on how to enroll authenticators for different factors:

You can also use the Universal Login flow for enrolling users at any time.

Delete authenticators

To delete an associated authenticator, send a DELETE request to the MFA Authenticators endpoint replacing the AUTHENTICATOR_ID with the relevant authenticator ID. You can get the ID when you list authenticators.

If an mfa_token was used to list authenticators, users will need to complete MFA to obtain an access token with an audience of https://YOUR_DOMAIN/mfa/ in order to delete an authenticator.


curl --request DELETE \
  --url 'https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID' \
  --header 'authorization: Bearer ACCESS_TOKEN'
var client = new RestClient("https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID");
var request = new RestRequest(Method.DELETE);
request.AddHeader("authorization", "Bearer ACCESS_TOKEN");
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID"

	req, _ := http.NewRequest("DELETE", url, nil)

	req.Header.Add("authorization", "Bearer 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.delete("https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID")
  .header("authorization", "Bearer ACCESS_TOKEN")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'DELETE',
  url: 'https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID',
  headers: {authorization: 'Bearer ACCESS_TOKEN'}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer ACCESS_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"DELETE"];
[request setAllHTTPHeaderFields:headers];

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, [
  CURLOPT_URL => "https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "DELETE",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer 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("")

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

conn.request("DELETE", "/YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID", headers=headers)

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

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

url = URI("https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID")

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

request = Net::HTTP::Delete.new(url)
request["authorization"] = 'Bearer ACCESS_TOKEN'

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

let headers = ["authorization": "Bearer ACCESS_TOKEN"]

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/mfa/authenticators/AUTHENTICATOR_ID")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "DELETE"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

If the authenticator was deleted, a 204 response is returned.

When you delete an authenticator, the following actions take place depending on the authenticator type:

Authenticator Action
Push and OTP When a user enrolls a push authenticator, Auth0 also enrolls OTP. If you delete either of them, the other one will also be deleted.
SMS and Voice When a user enrolls in either SMS or Voice,. Auth0 creates two authenticators, SMS and voice. If you delete either of them, the other will will also be deleted.
Email All verified emails listed as authenticators, but you can't delete them. You can only delete email authenticators that are enrolled explicitly.

Delete recovery codes

You can't delete recovery codes using the MFA API. You must use the Management API to delete recovery codes.

To delete a recovery code, get an Auth0 Management API access token and use the Management API Recovery Code Regeneration endpoint.


curl --request POST \
  --url 'https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration' \
  --header 'authorization: Bearer MANAGEMENT_API_TOKEN'
var client = new RestClient("https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration");
var request = new RestRequest(Method.POST);
request.AddHeader("authorization", "Bearer MANAGEMENT_API_TOKEN");
IRestResponse response = client.Execute(request);
package main

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

func main() {

	url := "https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration"

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

	req.Header.Add("authorization", "Bearer MANAGEMENT_API_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_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration")
  .header("authorization", "Bearer MANAGEMENT_API_TOKEN")
  .asString();
var axios = require("axios").default;

var options = {
  method: 'POST',
  url: 'https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration',
  headers: {authorization: 'Bearer MANAGEMENT_API_TOKEN'}
};

axios.request(options).then(function (response) {
  console.log(response.data);
}).catch(function (error) {
  console.error(error);
});
#import <Foundation/Foundation.h>

NSDictionary *headers = @{ @"authorization": @"Bearer MANAGEMENT_API_TOKEN" };

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];

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, [
  CURLOPT_URL => "https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_HTTPHEADER => [
    "authorization: Bearer MANAGEMENT_API_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("")

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

conn.request("POST", "/YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration", headers=headers)

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

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

url = URI("https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration")

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 MANAGEMENT_API_TOKEN'

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

let headers = ["authorization": "Bearer MANAGEMENT_API_TOKEN"]

let request = NSMutableURLRequest(url: NSURL(string: "https://YOUR_DOMAIN/api/v2/users/USER_ID/recovery-code-regeneration")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()

You will get a new recovery code that the end user will need to capture, for example:

{ "recovery_code": "FA45S1Z87MYARX9RG6EVMAPE" }

Learn more