Wrong password for imported users from Django

I have a Django app that I want to import the users from into Auth0.

I am able to do this but when I try and log in using my email and password which has been imported I get ‘Wrong email or password.’

I would like to know how to import these users and passwords correctly so they work in Auth0?

I have been following these guides:

https://auth0.com/docs/users/bulk-user-imports

I am using the Auth0 python API wrapper:

I have written a Django management command to import the users.

import io
import json
from base64 import b64encode

from auth0.v3.authentication import GetToken
from auth0.v3.management import Auth0
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand

User = get_user_model()


class Command(BaseCommand):
    """
    Take all users in database and import them to Auth0
    """
    help = 'Take all users in database and import them to Auth0'

    def handle(self, *args, **options):
        self.stdout.write('Generating users.json')

        users = []
        for user in User.objects.all():
            if not user.email:
                self.stdout.write(f'User {user.id} has no email.')
                continue

            if not user.password:
                self.stdout.write(f'User {user.id} has no password.')
                continue

            password_parts = user.password.split('$')

            password_parts[0] = password_parts[0].replace('_', '-')
            password_parts[2] = b64encode(password_parts[2].encode()).decode().replace('=', '')
            password_parts[3] = password_parts[3].replace('=', '')
            password_hash = f'${password_parts[0]}$i={password_parts[1]},l=64${password_parts[2]}${password_parts[3]}'

            user_dict = {
                'email': user.email,
                'email_verified': False,
                'custom_password_hash': {
                    'algorithm': 'pbkdf2',
                    'hash': {
                        'value': password_hash,
                        'encoding': 'utf8'
                    }
                }
            }

            users.append(user_dict)

        json_file = io.StringIO()
        json.dump(users, json_file)
        json_file.seek(0)

        self.stdout.write('Importing users to Auth0')

        get_token = GetToken(settings.SOCIAL_AUTH_AUTH0_DOMAIN)
        token = get_token.client_credentials(
            settings.SOCIAL_AUTH_AUTH0_KEY,
            settings.SOCIAL_AUTH_AUTH0_SECRET,
            f'https://{settings.SOCIAL_AUTH_AUTH0_DOMAIN}/api/v2/'
        )
        mgmt_api_token = token['access_token']

        auth0 = Auth0(settings.SOCIAL_AUTH_AUTH0_DOMAIN, mgmt_api_token)

        response = auth0.jobs.import_users(settings.SOCIAL_AUTH_AUTH0_CONNECTION_ID, json_file)

        self.stdout.write(str(response))

        json_file.close()

Here is an example of how a password is stored (for a fake user) in Django and the hash value sent to Auth0.

pbkdf2_sha256$150000$olC2KpMR07WB$onWQOO++v7VYL1e5J6+Ma53f32MxQBGjG4ZANvfEbSM=

$pbkdf2-sha256$i=150000,l=64$b2xDMktwTVIwN1dC$onWQOO++v7VYL1e5J6+Ma53f32MxQBGjG4ZANvfEbSM

I have resolved this issue here and will leave the complete management command incase someone else would like to use it in the future to export/import their users from Django to Auth0.

It turns out I was bamboozled by this comment in the Django source.

class PBKDF2PasswordHasher(BasePasswordHasher):
    """
    Secure password hashing using the PBKDF2 algorithm (recommended)

    Configured to use PBKDF2 + HMAC + SHA256.
    The result is a 64 byte binary string.  Iterations may be changed
    safely but you must rename the algorithm if you change SHA256.
    """

I thought this meant l=64. However after some experimentation I discovered l=32. Changing this line in my management command resolved the issue.

password_hash = f'${password_parts[0]}$i={password_parts[1]},l=32${password_parts[2]}${password_parts[3]}'

Here is the complete management command with the corrected keylen.

import io
import json
from base64 import b64encode

from auth0.v3.authentication import GetToken
from auth0.v3.management import Auth0
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand


User = get_user_model()


class Command(BaseCommand):
    """
    Take all users in database and import them to Auth0
    """
    help = 'Take all users in database and import them to Auth0'

    def handle(self, *args, **options):
        self.stdout.write('Generating users.json')

        users = []
        for user in User.objects.all():
            if not user.email:
                self.stdout.write(f'User {user.id} has no email.')
                continue

            if not user.password:
                self.stdout.write(f'User {user.id} has no password.')
                continue

            password_parts = user.password.split('$')

            password_parts[0] = password_parts[0].replace('_', '-')
            password_parts[2] = b64encode(password_parts[2].encode()).decode().replace('=', '')
            password_parts[3] = password_parts[3].replace('=', '')

            password_hash = f'${password_parts[0]}$i={password_parts[1]},l=32${password_parts[2]}${password_parts[3]}'

            user_dict = {
                'email': user.email,
                'email_verified': True,
                'custom_password_hash': {
                    'algorithm': 'pbkdf2',
                    'hash': {
                        'value': password_hash,
                        'encoding': 'utf8'
                    }
                }
            }

            users.append(user_dict)

        json_file = io.StringIO()
        json.dump(users, json_file)
        json_file.seek(0)

        self.stdout.write('Importing users to Auth0')

        get_token = GetToken(settings.SOCIAL_AUTH_AUTH0_DOMAIN)
        token = get_token.client_credentials(
            settings.SOCIAL_AUTH_AUTH0_KEY,
            settings.SOCIAL_AUTH_AUTH0_SECRET,
            f'https://{settings.SOCIAL_AUTH_AUTH0_DOMAIN}/api/v2/'
        )
        mgmt_api_token = token['access_token']

        auth0 = Auth0(settings.SOCIAL_AUTH_AUTH0_DOMAIN, mgmt_api_token)

        response = auth0.jobs.import_users(settings.SOCIAL_AUTH_AUTH0_CONNECTION_ID, json_file)

        self.stdout.write(str(response))

        json_file.close()
3 Likes

Thanks for sharing that with the rest of community!