Migrate Users to Auth0

Auth0 supports automatic migration of users from a custom database connection to Auth0. By activating this feature, your users are moved to Auth0 the first time they log in after you set up the integration. Your users are not asked to reset their password as a result of the migration.

Feature availability

  • Only Developer, Developer Pro, and Enterprise subscription plans include the database migration feature.
  • Only Enterprise subscription plans include the ability to connect to an existing store or database via JavaScript running on Auth0's servers for every authentication request.

For more information refer to Auth0 pricing plans.

The Migration Process

When a user authenticates via a custom database connection marked for import to Auth0, the following process takes place:

Migration Diagram

Auth0 authenticates migrated users against the Auth0 database.

If the user has not been migrated, Auth0 executes your custom login script and, upon successfully log in, adds the user to the Auth0 database.

Subsequent logins result in the user's credentials retrieved from Auth0, NOT your custom database.

New users are automatically added to the Auth0 database.

Auth0 can only assist users in the Auth0 database with password reset.

Enable Data Validation

There are certain validations that Auth0 can run before adding the user to the Auth0 database:

  • The email must be set and unique
  • If the connection requires a username (i.e. the Requires Username toggle is enabled at the connection's settings), then one must be set
  • If a username is set, then it must be unique
  • The email format must be valid

Additionally, both the email and the username will be converted to lowercase before they are stored.

You can enable these validations for a connection using the Update a connection endpoint of the Management APIv2.


curl --request PATCH \
  --url 'https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D' \
  --header 'authorization: Bearer {access-token}' \
  --header 'content-type: application/json' \
  --data '{ "options": { "strategy_version": 2, "nextOptionParam": "..." } }'
var client = new RestClient("https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D");
var request = new RestRequest(Method.PATCH);
request.AddHeader("content-type", "application/json");
request.AddHeader("authorization", "Bearer {access-token}");
request.AddParameter("application/json", "{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }", 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/connections/%7Bconnection-id%7D"

	payload := strings.NewReader("{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }")

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

	req.Header.Add("authorization", "Bearer {access-token}")
	req.Header.Add("content-type", "application/json")

	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.patch("https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D")
  .header("authorization", "Bearer {access-token}")
  .header("content-type", "application/json")
  .body("{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }")
  .asString();
var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D",
  "method": "PATCH",
  "headers": {
    "authorization": "Bearer {access-token}",
    "content-type": "application/json"
  },
  "processData": false,
  "data": "{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }"
}

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

var options = { method: 'PATCH',
  url: 'https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D',
  headers: 
   { 'content-type': 'application/json',
     authorization: 'Bearer {access-token}' },
  body: { options: { strategy_version: 2, nextOptionParam: '...' } },
  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 {access-token}",
                           @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"options": @{ @"strategy_version": @2, @"nextOptionParam": @"..." } };

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

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D"]
                                                       cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                   timeoutInterval:10.0];
[request setHTTPMethod:@"PATCH"];
[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/connections/%7Bconnection-id%7D",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => "",
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 30,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => "PATCH",
  CURLOPT_POSTFIELDS => "{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }",
  CURLOPT_HTTPHEADER => array(
    "authorization: Bearer {access-token}",
    "content-type: application/json"
  ),
));

$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 = "{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }"

headers = {
    'authorization': "Bearer {access-token}",
    'content-type': "application/json"
    }

conn.request("PATCH", "/YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D", 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/connections/%7Bconnection-id%7D")

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

request = Net::HTTP::Patch.new(url)
request["authorization"] = 'Bearer {access-token}'
request["content-type"] = 'application/json'
request.body = "{ \"options\": { \"strategy_version\": 2, \"nextOptionParam\": \"...\" } }"

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

let headers = [
  "authorization": "Bearer {access-token}",
  "content-type": "application/json"
]
let parameters = ["options": [
    "strategy_version": 2,
    "nextOptionParam": "..."
  ]]

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

var request = NSMutableURLRequest(URL: NSURL(string: "https://YOUR_AUTH0_DOMAIN/api/v2/connections/%7Bconnection-id%7D")!,
                                        cachePolicy: .UseProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.HTTPMethod = "PATCH"
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()
  • The parameter that enables the validations is the "strategy_version": 2. However, when you update options, the whole object is overridden, so all the parameters should be present. You can use the Get a connection endpoint to get the whole object. Replace the "nextOptionParam": "..." placeholder with the list of parameters you get.
  • You should replace {connection-id} with the Id of the custom database connection you want to update. If you don't know it, you can use the Get all connections endpoint to find it.
  • To access any Management APIv2 endpoint you need an Access Token (which you should set at the {access-token} placeholder of the Authorization header). For information on how to get one refer to The Auth0 Management APIv2 Token.

Enable Automatic Migration

1. Create a Custom Database

You can create a new database connection in the Connections > Database section of the Dashboard.

On the Custom Database page, enable the Use my own database option:

DB Login Page in Dashboard

2. Turn on Automatic Migration

On the Settings page for your database, enable the Import Users to Auth0 option:

Dashboard Import Users Option

3. Configure the Database Action Scripts

On the Custom Database page, under Database Action Scripts, you will see the Login and GetUser scripts you need to configure.

Database Action Scripts page

These custom scripts are Node.js code that run in the tenant's sandbox. Auth0 provides templates for most common databases, such as: ASP.NET Membership Provider, MongoDB, MySQL, Oracle, PostgreSQL, SQLServer, Windows Azure SQL Database, and for a web service accessed by Basic Auth. For more information on implementing these scripts, see Authenticate Users using a Custom Database.

The Login script executes each time a user that is not found in Auth0 database attempts to log in. It verifies that the user exists in the legacy database without prompting the user for their password again.

The Get User script executes following any of these actions:

Passwords for Un-Migrated Users

If an un-migrated user confirms a password change, their user profile will be created in Auth0 with the new password. This user profile will contain all the information returned in the Get User script. All subsequent logins of this user will be performed in Auth0 directly.

You may see unexpected behavior if you return differing user profiles in the login and get_user scripts.

4. Complete the Migration

After you've enabled migration, you can verify the users that have migrated by:

Database Users

Once all your users are in the Auth0 database, you are ready to turn off the import users feature and convert the database to Auth0.

  1. Go to your custom database connection on the Dashboard.

  2. Update the Login Database Action Script to the following:

function login (email, password, callback) {
  return callback(null, null);
}
  1. Update the Get User Database Action Script to the following:
function getByEmail (email, callback) {
  return callback(null, null);
}

By doing this, you are changing the Login and Get User Database Action Scripts to NO-OP functions. You will need to implement the other script types for sign up, email verification, password reset and delete user functionality on the Auth0 database that you have migrated to. Click here to learn more about the action scripts for your custom database.

At this point, you can disconnect your legacy database (not the Auth0 database). Your users will then be directed to use the new database workflow. You can configure rules to execute other functions when a user authenticates to your application.