So I previously blogged about replicating Meteor's password hashing, so that migration off of meteor could be (somewhat) painless. While I had it working for JWT logins with Persona in my code, I couldn't get it working with basic auth.
I have some paths that serve up static files (swagger documentation and so forth) for internal consumption only, so I have a middleware that checks for the existence of a flag indicating if a user is an 'internal' user or not, and then I also attached basic auth to the routes for these things. Unfortunately, due to the double hashing we need to use to validate a meteor-ized password, basic auth just didn't want to work.
As AdonisJS is awesome, and uses dependency injection, I knew it had to be possible to alter the authentication scheme being used in a not terribly difficult manner.
Ultimately it is not terribly difficult, but it's also not very well documented, and thus it was very difficult finding the not difficult solution.
Initially I thought, hey I'll just extend the basic auth scheme, and change what gets passed to the password validation method. That worked, but a brief interaction with Virk, Adonis' creator, on discord led me in a better direction...
Extend the default lucid serializer instead of the basic auth scheme. This way, the same password logic can be used for all auth methods.
So, first I added a custom serializer in app/Serializers
called MeteorHashSerializer.js
:
'use strict'
const LucidSerializer = require('@adonisjs/auth/src/Serializers/Lucid')
const Crypto = require('crypto')
class MeteorHashSerializer extends LucidSerializer {
async validateCredentails (user, password) {
if (!user || !user[this._config.password]) {
return false
}
return this.Hash.verify(
Crypto.Hash('sha256').update(password).digest('hex'),
user[this._config.password]
)
}
}
module.exports = MeteorHashSerializer
That simply overrides the validateCredentials()
method of the default lucid serializer, and alters out hash verify logic. Pretty easy so far...
Next, we add a hook to our start/hooks.js
file, thusly:
const { ioc } = require('@adonisjs/fold')
const { hooks } = require('@adonisjs/ignitor')
const MeteorHashSerializer = require('../app/Serializers/MeteorHashSerializer')
hooks.before.providersRegistered(() => {
ioc.extend('Adonis/Src/Auth', 'meteorhash', (app) => {
const Hash = use('Hash')
return new MeteorHashSerializer(Hash)
}, 'serializer')
})
This extends the auth provider, attaching our new custom serializer to it.
Finally, we update the config/auth.js
with the new serializer. You can see in our hook that we called it meteorhash
:
basic: {
serializer: 'meteorhash',
model: 'App/Models/User',
scheme: 'basic',
uid: 'email',
password: 'password'
},
It was challenging to get to this point. I love this framework, but some of the gaping holes in the documentation can be frustrating.