Migrating Users and Orgs from Auth0

Migrating your users and organizations from one auth source to another can seem scary. Fortunately, we're here to help! See our Migrating Users to PropelAuth guide for more general information or stay here for an advanced guide, including code snippets, on how to migrate your users and orgs from Auth0 to PropelAuth.

Exporting Users from Auth0

You can easily export a JSON of your user data from Auth0 using Auth0’s User Import / Export extension. Follow the directions found here to install the extension.

Next, click on Export to navigate to the export menu. From there, add default fields as well as any others you want to export over to PropelAuth. Don't worry about orgs for now, we'll get to those later.

Auth0 does not export password hashes via their API or this extension. You’ll need to contact Auth0 support for an export of your user’s passwords. Here’s some more documentation on that.

Make sure to export as a .json then click the Export Users button.

auth0 export

Since we now have two .json files, one with user information and one with user passwords, we have to combine the two on their matching user Ids before we import the data into PropelAuth. Before we start, let's install the pandas library:

pip install pandas

Running the script below will create a new file called auth0_users.json that will contain the user data to import into PropelAuth. Make sure to adjust the two variables at the top of the script before running.

import pandas as pd

# Edit these fields to point to the two json files from Auth0
user_export_filename = './auth0-export.json'
password_hash_filename =  './auth0-password-export.json'

def load_password_hashes_from_user_ids(filename):
    password_hash_df = pd.read_json(filename, lines=True)
    password_hash_df['_ID'] = password_hash_df['_ID'].apply(lambda v: "auth0|" + v["$oid"])
    password_hash_df = password_hash_df.rename(columns = {"_ID": "Id", "passwordHash": "Password Hash"})
    return password_hash_df[["Id", "Password Hash"]]

def load_user_data(filename):
    return pd.read_json(filename, lines=True)

def load_combined_user_data_and_password_hashes(user_export_json_filename, password_hash_json_filename):
    id_and_password_hash = load_password_hashes_from_user_ids(password_hash_json_filename)
    user_export_df = load_user_data(user_export_json_filename)
    return pd.merge(id_and_password_hash, user_export_df, on="Id", how="outer")

auth0_users = load_combined_user_data_and_password_hashes(user_export_filename, password_hash_filename)
auth0_users.to_json("auth0_users.json", orient="records", lines=True)

Importing Users into PropelAuth

We can then use auth0_users.json to import your user data to PropelAuth. PropelAuth offers an API endpoint to make migrating users as simple as possible. Before we run the script, let's install PropelAuth's Python library:

pip install propelauth_py

This script will attempt to import each of the users found in your auth0_users.json file into PropelAuth. If any of the imports fail, it will automatically generate a ./unmigrated_users.json file for you.

To run the script we need to get your PROPELAUTH_AUTH_URL and PROPELAUTH_AUTH_API_KEY, both of which can be found in your Backend Integration page in PropelAuth.

from propelauth_py import init_base_auth
import json

# Replace with your PropelAuth Auth URL and API key
PROPELAUTH_AUTH_URL = "https://auth.yourdomain.com"
PROPELAUTH_AUTH_API_KEY = "b1988511..."

# replace with your JSON path
auth0_json_filename = "./auth0_users.json"

# If we fail to migrate any users, we'll output them here
unmigrated_users_filename = "./unmigrated_users.json"

auth = init_base_auth(PROPELAUTH_AUTH_URL, PROPELAUTH_AUTH_API_KEY)

def create_propelauth_user(user_data):
    auth.migrate_user_from_external_source(
        email = user_data.get('email'),
        email_confirmed = user_data.get('email_confirmed'),
        existing_user_id = user_data.get('id'),
        existing_password_hash = user_data.get('password_hash'),
        ask_user_to_update_password_on_login = False,
        enabled = True,
        username = user_data.get('username'),
        first_name = user_data.get('first_name'),
        last_name = user_data.get('last_name'),
        properties = user_data.get('properties'),
    )
    print(f"API call successful for user: {user_data.get('id')}")

def process_users():
    users_with_errors = []
    
    with open(auth0_json_filename, "r") as auth0_json_file:
        # Iterate through each user row
        for row_str in auth0_json_file.readlines():
            row = json.loads(row_str)
            try:
                create_propelauth_user({
                    'id': row.get('Id'),
                    'first_name': row.get('Given Name'),
                    'last_name': row.get('Family Name'),
                    'email': row.get('Email'),
                    'password_hash': row.get('Password Hash'),
					'email_confirmed': row.get('Email Verified'),

                    # Fields below this are dependent on your setup, you may need to modify
                    #'username': row.get('Username'),
                    # Any custom user properties that aren't first_name, last_name, or username go here
                    #'properties': {
                    #    'sample_custom_property': row['sample']
                    #}
                })
            except Exception as e:
                users_with_errors.append(row)
                print(f"Error during processing user: {row['Id']}")
                print(f"Error: {e}")

    if len(users_with_errors) > 0:
        with open(unmigrated_users_filename, "w") as unmigrated_users_file:
            for failed_user in users_with_errors:
                unmigrated_users_file.write(json.dumps(failed_user) + "\n")

        print(f"We couldn't migrate all users - the users we failed to migrate are here {unmigrated_users_filename}")

# Run the script
if __name__ == "__main__":
    process_users()

Need a do over? Here's a script to quickly delete all of your users so you can try again.

import requests
from propelauth_py import init_base_auth

# Replace with your PropelAuth Auth URL and API key
PROPELAUTH_AUTH_URL = "https://auth.yourdomain.com"
PROPELAUTH_AUTH_API_KEY = "b1988511..."

auth = init_base_auth(PROPELAUTH_AUTH_URL, PROPELAUTH_AUTH_API_KEY)

def get_users():
    try:
        response = auth.fetch_users_by_query(
            page_size = 10,
            page_number = 0
        )
        return response
    except requests.exceptions.RequestException as e:
        print(f"Error making API call to retrieve users")
        print(f"Error: {e}")

def delete_user(user_id):
    try:
        auth.delete_user(user_id)
    except requests.exceptions.RequestException as e:
        print(f"Error making API call to delete user {user_id}")
        print(f"Error: {e}")

def delete_users():
    user_response = get_users()
    while len(user_response['users']) > 0:
        for user in user_response['users']:
            delete_user(user['user_id'])
        user_response = get_users()

# Run the script
if __name__ == "__main__":
    delete_users()

Exporting Orgs From Auth0

We now need to export your organization data. Auth0 does not have a native way to do this via their UI so we’ll have to use their Management API instead. This means we first need to install the Requests library.

pip install requests

Gather your Auth0 domain and API token and run this script to create a json containing all of your org and org membership data.

import json
import requests
from propelauth_py import init_base_auth

# Replace with your Auth0 base url and api token
AUTH0_BASE_URL="https://yourdomain.us.auth0.com"
AUTH0_API_TOKEN="eyJh..."

# Replace with your PropelAuth Auth URL and API key
PROPELAUTH_AUTH_URL = "https://auth.yourdomain.com"
PROPELAUTH_AUTH_API_KEY = "b1988511..."

auth = init_base_auth(PROPELAUTH_AUTH_URL, PROPELAUTH_AUTH_API_KEY)

def get_propelauth_user(email):
    try:
        user = auth.fetch_user_metadata_by_email(email)
        return user['user_id']
    except requests.exceptions.RequestException as e:
        print(f"Error making API call to get propelauth user: {email}")
        print(f"Error: {e}")

def get_org_members(org_id):
    total_members = []
    more_members = True
    headers = {
        'Authorization': f'Bearer {AUTH0_API_TOKEN}'
    }
    page=0
    while more_members:
        request = requests.get(url=f"{AUTH0_BASE_URL}/api/v2/organizations/{org_id}/members?fields=roles,user_id,email&include_fields=true&page={page}", headers=headers)
        org_members = request.json()
        if len(org_members) > 0:
            total_members += org_members
            page += 1
        else:
            more_members = False
    for member in total_members:
        member['propelauth_user_id'] = get_propelauth_user(member['email'])
        
    return total_members

def export_orgs():
    total_orgs = []
    more_orgs = True
    headers = {
        'Authorization': f'Bearer {AUTH0_API_TOKEN}'
    }
    page=0
    while more_orgs:
        request = requests.get(url=f"{AUTH0_BASE_URL}/api/v2/organizations?page={page}", headers=headers)
        orgs = request.json()
        if len(orgs) > 0:
            for org in orgs:
                org_members = get_org_members(org['id'])
                org['members'] = org_members
            total_orgs += orgs
            page += 1
        else:
            more_orgs = False
            
    with open("org_data.json", "w") as outfile:
        json.dump(total_orgs, outfile)
        
if __name__ == "__main__":
    export_orgs()

Importing Orgs into PropelAuth

You should now have a file called org_data.json that contains all of your orgs, who belongs to each org, and which role the user belongs to for each org. Lets import this data using PropelAuth's API.

import json
import requests
from propelauth_py import init_base_auth

# Replace with your PropelAuth Auth URL and API key
PROPELAUTH_AUTH_URL = "https://auth.yourdomain.com"
PROPELAUTH_AUTH_API_KEY = "b1988511..."

# replace with your JSON path
ORG_JSON = "./org_data.json"

auth = init_base_auth(PROPELAUTH_AUTH_URL, PROPELAUTH_AUTH_API_KEY)

def map_auth0_role_to_propelauth_role(user_roles):
  # implement me
  return user_roles[0]['name']

def create_propelauth_org(org_data):
    try:
        response = auth.create_org(
            name = org_data['name']
        )
        print(f"API call successful to create org: {org_data['name']}")
        return response
    except requests.exceptions.RequestException as e:
        print(f"Error making API call to create org: {org_data['name']}")
        print(f"Error: {e}")

def add_user_to_org(org_id, org_user):
    try:
        auth.add_user_to_org(
            user_id=org_user['propelauth_user_id'],
            org_id=org_id,
            role=map_auth0_role_to_propelauth_role(org_user['roles']),
        )
        print(f"API call successful to add user {org_id} to org {org_id}")
    except requests.exceptions.RequestException as e:
        print(f"Error making API call to add user {org_id} to org {org_id}")
        print(f"Error: {e}")

def process_orgs():
    with open(ORG_JSON, 'r') as infile:
        org_data = json.load(infile)

        for org in org_data:
            create_org_response = create_propelauth_org({
                'name': org['name']
            })
            org_id = create_org_response['org_id']
            for member in org['members']:
                add_user_to_org(org_id, member)

# Run the script
if __name__ == "__main__":
    process_orgs()

All of your orgs and users are now imported! If you run into any problems, do not hesitate to reach out to support@propelauth.com.