otp
otp copied to clipboard
Example on how to handle recovery codes?
Forgive me if this is a stupid question, how exactly can I handle recovery codes for users?
In README, you said "These can simply be randomly generated strings that you store in your backend" but I could not find the code that do this.
Does that mean I need to handle recovery codes myself? I was thinking along the lines of
- Generate recovery codes in backend
- Give users recovery codes
- Compare passcode with recovery codes (pulled from backend storage). If not matched, then compare passcode with TOTP / HOTP as usual? If matched, remove recovery codes (one time use like Github)
Let me know if you have plan to support this behavior natively in this package, or if you are interested in a Pull Request that does this (backend storage via an interface, of course)?
i'd be ok with a sub-module for recovery codes.
i'm a little concerned about scope creep - as you allude to, recovery codes inherently need storage. Additionally, you probally want to either hash or encrypt them - ideally they aren't stored in clear text.
Just sketching out how this could work, to avoid storing recovery codes in clear text:
- Generate Function with Options (length, number of codes), returns array of strings
- Transform function takes varargs strings input, applies a sha256() and returns an array of strings
To validate:
- Get recovery code from user - most UX have this as an explicit option, eg, "Can't find your phone, enter a recovery code here", and not implicitly based on overloading the input box.
- Call Transform() with user input, this would return the hashed version.
- Lookup in your backend for a given user_id, does the transformed string exist in a set of backup codes, if it does, delete the used one, and continue the login flow.
I'm not sure step 3 having an interface is a big help - its going to be very app specific so I'm wondering if we should instead focus on easy to use composable functions with a few examples, what do you think?
@pquerna Is this issue stale? I was writing a recovery code module for myself the other day using 4-letter English words to encode a binary code. Argon2di for hashing. I could contrib it here. Interested?
Hi,
I don't think storing a hash for recovery codes is a good idea for all use cases. For example GitHub allows you to view the plain recovery codes at any time and this is not possible if you hash the recovery codes.
I'm adding TOTP support to SFTPGo and I'm using a struct like the following one to keep recovery codes
// RecoveryCode defines a 2FA recovery code
type RecoveryCode struct {
Secret *kms.Secret `json:"secret"`
Used bool `json:"used,omitempty"`
}
kms.Secret
is an encrypted secret that can be stored within external KMS such as Vault, GCP KMS, AWS KMS and so on. This way the recovery code is stored encrypted and can be decrypted at any time so I can show the plain text recovery codes to the users at any time as GitHub. I can also show the used recovery codes.
This feature is very application specific and I think it is out of the scope of the library
@dkotik a sub-module for recovery code generation seems helpful. If it pulls in other deps, might drop it in a new go.mod
scope, but would welcome a PR with it.