Bulk User Import Custom Password Hash Issue

I am attempting to import users, including their passwords, from Discourse to Auth0. I’m following these guides:

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

Step 1 Export users from Discourse

Using Discourse’s nifty Data Explorer Plugin, I’ve used this query to generate a list that includes email, email_verified and password_hash.

Query
SELECT
    user_emails.email,
    users.active as email_verified,
    concat(
        '$pbkdf2-sha256$i=64000,l=32$',
        users.salt,
        '$',
        replace(encode(decode(users.password_hash, 'hex'), 'base64'), '=', '')
    ) as password_hash
FROM users
INNER JOIN user_emails 
ON users.id = user_emails.user_id 
AND user_emails.primary IS TRUE
AND users.id > 0

Note that I’ve based the PHC string generation on the example given by the Discourse team. As far as I can tell this follows the PHC standard. Here’s an example string

$pbkdf2-sha256$i=64000,l=32$eedf758e855bf13f96f9c48c14d486fb$3t2t87idnzb3Xzqwi0zXn95uJBwKZlo5Sk4yCg4jxR8

Password hashing in Discourse is performed here. And the relevant configuration is here. The hashing and configration have not changed since the PHC “how-to” doc linked above was written.

Step 2 Create users JSON file

I’ve used this ruby script to convert the Discourse users JSON to an Auth0 users JSON

Generate Auth0 JSON
#!/usr/bin/ruby
require 'json'

input = JSON.parse(File.read(ARGV[0]))
output = []

input["rows"].each do |row|
  output.push(
    email: row[0],
    email_verified: row[1],
    custom_password_hash: {
      algorithm: "pbkdf2",
      hash: {
        value: row[2],
        encoding: "utf8"
      }
    }
  )
end

File.open("output.json", "w") do |f|
  f.write(output.to_json)
end

Here’s an example user JSON produced by that script

{
    "email": "angus+1@fake-email.com",
    "email_verified": true,
    "custom_password_hash": {
      "algorithm": "pbkdf2",
      "hash": {
        "value": "$pbkdf2-sha256$i=64000,l=32$eedf758e855bf13f96f9c48c14d486fb$3t2t87idnzb3Xzqwi0zXn95uJBwKZlo5Sk4yCg4jxR8",
        "encoding": "utf8"
      }
    }
 }

Step 3 Post users JSON file to Auth0

I’ve used this ruby script to post the JSON file to Auth0

Create Auth0 Import
#!/usr/bin/ruby

require 'rest-client'

response = RestClient.post(
  "https://***.auth0.com/api/v2/jobs/users-imports", {
    users: File.new("output.json"),
    connection_id: "***",
    send_completion_email: true
  }, {
    "authorization": "Bearer  ***",
    "content-type": "multipart/form-data"
  }
)

puts response.body

The import succeeds with no errors. Upon completion, I see the users I’ve imported in the Auth0 dashboard, with the correct email and email_verified attributes.

Step 4 Attempt to sign in

On attempting to sign in with an imported user I’m getting this error in the UI:

Screen Shot 2020-08-04 at 2.19.22 PM

And this error in the logs:

"error": {
      "message": "Password change required.",
      "reason": "Verification failed for the provided custom_password_hash: {'algorithm':'pbkdf2','hash':{'value':'$pbkdf2-sha256$i=64000,l=3...','encoding':'utf8'},'salt':{'value':''}}"

Aside from an issue with the PHC string which I’m not seeing, the only other thing that stands out is that there is an empty “salt” value in the error.reason.

'salt':{'value':''}

As you’ll notice in the users JSON file example, no seperate salt value has been included in the import file. Perhaps this is just a standard logging output, but that could be the issue.

Any pointers or hints would be much appreciated :slight_smile:

Hi @angus, Welcome to the Auth0 Community!

Format of the import looks good to me. To confirm did you convert the original salt to B64 format before putting in the PHC string as defined above in the user import?
It would also help for me to double confirm the hash generated, can you DM me your sample password and salt which you used for the test to verify if my values match.

Looking forward to hear back!

Regards,
Sidharth Chaudhary

1 Like

@angus, Thanks for the information , In the user import the salt will need to be converted to B64 format

E.g your original salt is : “eedf758e855bf13f96f9c48c14d486fb” → “ZWVkZjc1OGU4NTViZjEzZjk2ZjljNDhjMTRkNDg2ZmI=”(base64) → “ZWVkZjc1OGU4NTViZjEzZjk2ZjljNDhjMTRkNDg2ZmI”(B64)

User this in your user import: E,g

{
“email”: “angus+1@fake-email.com”,
“email_verified”: true,
“custom_password_hash”: {
“algorithm”: “pbkdf2”,
“hash”: {
“value”: “$pbkdf2-sha256$i=64000,l=32$ZWVkZjc1OGU4NTViZjEzZjk2ZjljNDhjMTRkNDg2ZmI$3t2t87idnzb3Xzqwi0zXn95uJBwKZlo5Sk4yCg4jxR8”,
“encoding”: “utf8”
}
}
}

Can you give this a go and try again?

Regards,
Sidharth

2 Likes

Thanks @sidharth.chaudhary,

This Discourse user import flow is now working with this updated query in Step 1

SELECT
    user_emails.email,
    users.active as email_verified,
    concat(
        '$pbkdf2-sha256$i=64000,l=32$',
        replace(encode(users.salt::bytea, 'base64'), '=', ''),
        '$',
        replace(encode(decode(users.password_hash, 'hex'), 'base64'), '=', '')
    ) as password_hash
FROM users
INNER JOIN user_emails 
ON users.id = user_emails.user_id 
AND user_emails.primary IS TRUE
AND users.id > 0

Note that the difference was this line

replace(encode(users.salt::bytea, 'base64'), '=', '')

Cheers :slight_smile:

2 Likes

Perfect! Glad to hear that!

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.