OTP
Users will enter email and password which backend checks
- If credentials are valid,
- And TOTP is enabled,
- Then return
417 Expectation Failed
response, - Given
HTTP 417
response, - Then clients should present TOTP code with the same authentication credentials.
mermaid
%%{init: {'theme':'dark'}}%%
flowchart TD
A(Login) --> B{Credentials valid?}
B --> |Yes| C
B --> |No| G
C{MFA Enabled?}
C --> |No TOTP Code given| I
C --> |No| H
C --> |Yes| D
D{TOTP Valid?}
D --> |Yes| H
D --> |No| F
F[Respond: HTTP 400]
G[Respond: HTTP 401]
H[Return Auth Tokens]
I[Respond: HTTP 417]
OTP activation
When users enable MFA the following things happen:
- Endpoint
POST /mfa/enable
generates TOTP secret, - Sends QR code as base64 image and TOTP secret,
- Client scans and creates OTP client,
- Then enters TOTP code which,
- Then sent to
POST /mfa/setup
alongside with secret, - On backend we encrypt TOTP code,
- Then generate backup codes,
- Then encrypt backup codes,
- Then save TOTP secret and backup codes and secure encryption key,
- Then return backup codes to user,
- Done.
Schema level
Users' table and schemas have the following fields the last two of which are encrypted with private key and if users have MFA enabled we decrypt TOTP secret to verify given TOTP code. To reset MFA we also keep backup codes in encrypted form and only decrypt to verify if given backup code is correct.
Both encrypted fields have the following format system_key_hash:base64_key_contents
.
py
class User:
...
mfa_enabled: bool
mfa_encrypted_secret: SomeStr
mfa_encrypted_backup_codes: SomeStr