On 6/30/23 08:13, Nick Couchman wrote:
I decided to take a shot at writing a One-Time Password extension that
sends the OTP via e-mail, providing multi-factor authentication by
implementing it as a decorating authentication extension. I've got
quite a bit of it written and mostly working, but I'm running into one
behavior that I cannot figure out.

One of my criteria when implementing this extension was that the OTP
communicated via e-mail should only be used one time - that is, as
soon as it is used, it is invalidated and/or removed from the storage
mechanism that tracks the OTPs for the user. Unfortunately this is
causing an issue, because it seems that the decorate() method for the
UserContext object gets called twice, which means the OTP validation
happens twice. I'm not sure if this is because it's getting called to
decorate both the Postgresql and Postgresql Shared authentication
providers? I've gone through my code a few times to make sure that I'm
not accidentally calling it multiple times, myself, and I'm not seeing
that.

So, my questions are:
1) Is this (multiple decorations/verifications during a single login) expected?

Yep - it'll be called for every UserContext to provide an opportunity to decorate that specific UserContext. Depending on how many extensions are installed and how many AuthenticationProviders return UserContexts for that auth attempt, there could be any number of these.

2) Assuming it is expected, any guidance on how to implement in a way
that works with this? I suppose I could relax the requirement for only
using it once and allow it to be used multiple times during the time
it is valid, but ideally it would truly be a single-use password.

You could track based on uniqueness of the email address, not resending the code so long as there is an existing code having already been sent to that email, maintaining sent codes in memory.

You'll end up in a similar situation to what we were encountering with SAML+TOTP where anti-replay defenses conflict. You can work around that with the same approach (pending merge): allow reuse of the code/link while authentication is being refused only for transient reasons (insufficient credentials, non-security client exceptions), and fully invalidate the code/link only after authentication has 100% succeeded (auth success event) or 100% failed (auth failure event from any other exception).

- Mike

Reply via email to