Handling TOTP

Currently building authentication for a mobile app, and the flow goes as follows:

User inputs phone # > User receives TOTP SMS > User inputs TOTP > User is logged in

Would you store TOTP in DB or in memory (ETS or GenServer)?

Database. It lets you run multiple versions of your app easily, and if your app goes offline, that doesn’t nuke people’s code.

GenServers / ets are a great way to store ephemeral state or cache values. TOTP codes are short lived but not ephemeral.

7 Likes

That answer was FAST man :hot_face:

Makes sense, follow up question, would you have a separate table for them, or just stick them in the account/user table ?

What’s the difference ?

It means that if you store them in memory and your server goes off or restarts before the user receives the TOPT and inputs it, then the application will not recognize the valid TOPT and the user is forced to repeat the process.

4 Likes

Definitely a separate table. You want to track individual issuances of these codes.

3 Likes

For what purpose ? I naively assumed that even though they would be in a separate table, that they should be cleaned up after verification.

Hmm on second thought, attempt limiting I guess?

Yeah it’s worth cleaning them out eventually but we like to keep them around for customer support and security purposes for a bit. If someone is having issues it’s easier to help if you can see all of their attempts.

3 Likes

Why store individual TOTPs at all? " Time-based" in TOTP mean that knowing shared secret you can always generate all past and future tokens, so storing them is pointless.

@WestKeys probably meant OTP not specifically TOTP. TOTP do not need to be stored at all (except the shared secret).

2 Likes

Yes you’re right. Naively assumed OTP’s with an expiry == TOTP.

I would highly suggest “real TOTP” instead of SMS based 2FA, as the latter can work even with offline devices. It is also much easier and cheaper to implement.

I have not used it personally but you should check out NimbleTOTP v0.1.1 — Documentation, it seems as though they have implemented most of what you need already. Just store the secret after the user has confirmed it and then you can get codes from an authenticator app or text them to a user yourself.

That will not really work, as the lifetime of the TOTP can be too narrow for such unreliable messaging system like SMS.

I’ve never implemented such a system myself so you’re probably right, but I know that I can get a text message with a 2fa code from basically every provider who offers 2fa, so there must be a way to do that.

It probably uses exactly what OP is trying to do - generate token that have some timeout and is assigned to your account. Now you just send message and wait for a code. The problem with using TOTP is that these have (by default) have 30s lifetime, and using the same secret with different lifetimes can cause problems AFAIK. Of course you could generate several secrets and then just use different when trying different auth method (with different lifetimes).

Pretty sure NimbleTOTP and TOTP in general requires authenticator app, and can’t support SMS workflow. The verification code is actually generated by the authenticator app itself, based on the secret provided to it by, in this case, NimbleTOTP.

I may be wrong. All ears.

I’m building a chat app. Imagine the onboarding friction of having to download a 2nd app for 95% of users.

I do agree though that TOTP is cheaper, easier and much more secure.

If that is corporate, then screw all that and force SAML/LDAP/OpenID/other centralised corporate authentication system. Also many password managers already support TOTP generators (for example 1password does) and some corporations require usage of such.

If it is “civil” user facing, then it can be a little bit irritating, but maybe email would be easier in such environment instead of SMS (I assume that login is email-based, if it is phone-based, then SMS probably make more sense).

2 Likes

Do we also store the secrets from the TOTP algorithm in the same table or separate? Thanks!!

EDIT: just found this and it suggests that we would store it in a separate table:

super helpful reference in case anyone else needs it!

1 Like