How to Migrate Passwords from Bcrypt to Argon Hashes in AdonisJS
You aren't alone on this. I've had to migrate several production applications which were running with Adonisjs V4 (using the bcrypt
hashing algorithm) to Adonisjs V5 (using the argon
hashing algorithm). The challenge here is ensuring existing users (before the migration) can still log in seamlessly.
Here is how to ensure that your users whose passwords were hashed with bcrypt
can still login while progressively migrating their password hashes to the argon
algorithm.
- In your
User
model, add a method to check if the user's password is still hashed with thebcrypt
algorithm:
public usingOldPassword(oldPassword?: string) {
const self = this
const password = oldPassword ?? self.password
return !!password && ['$2a$', '$2b$', '$bcrypt'].some((pattern) => password.startsWith(pattern))
}
In the above usingOldPassword
method, self
points at this
- which is an instance of the User
model. The oldPassword
parameter is the user's password hash and is optional. If not supplied from the outside, it uses the user's password hash stored on the database.
bcrypt
password hashes start with either $2a$
or $2b$
, or $bcrypt
. See: https://en.wikipedia.org/wiki/Bcrypt
- In your password verification logic, verify the user's password with either
bcrypt
orargon
:
/**
* Verify password
*/
const passwordMatched = user.usingOldPassword()
? await bcrypt.compare(this.payload.password, user.password)
: await Hash.verify(user.password, this.payload.password)
if (!passwordMatched) {
// Handle as your wish
}
In the above snippet, we check if the user is using the old password. If true
, we compare the password hash using bcrypt
. If false
, we compare using the default Adonisjs hashing driver which is argon.
- Before you generate the token, overwrite the
bcrypt
hash with theargon
hash:
if (user.usingOldPassword()) {
await user.merge({ password }).save()
}
token = await auth!.use('api').attempt(email, password, { expiresIn: loginExpiresAt })
return token
The above snippet will resave the user's password using the argon
algorithm if the user is still using the bcrypt
hash. Ensure that you have the following beforeSave
hook installed:
@beforeSave()
public static async hashPassword(user: User) {
if (user.$dirty.password) {
user.password = await Hash.make(user.$dirty.password)
}
}
This strategy could be applied for migrating password hashes from any hash algorithm to another with little adaptation - especially for hashing algorithms which require secrets.
If this worked for you or you have improvements, please drop a comment. All the best with your hash migrations ๐
Subscribe to my newsletter
Read articles from Ndianabasi Udonkang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ndianabasi Udonkang
Ndianabasi Udonkang
I'm a full-stack software software with over 8 years experience in various tech fields including Linux server administration, AWS/Azure/Google Cloud cloud architecture, web designs, and software development. Currently as Senior Backend Engineer at Cavai Advertising.