Adding two-factor authentication

Decisions on how to include 2fa at

Last year we added two-factor authentication (2fa) as an option to increase the level of security for our users. In face of rising phishing attacks (our own team has been increasingly targeted in the past months), it makes sense to allow our users to have the option of making sure their funds are safe even when passwords are compromised. We also wanted to ensure our own team is protected against the eventuality of a successful phishing attack.

Fishing or Phishing? (Photo: Maurits Verbiest. cc-by)

We’re one of the few equity crowdfunding platforms authorised to hold client money, allowing investors to deposit and withdraw funds via bank card or bank transfer. In our particular platform, there are two points of interest for a potential attacker:

  1. Withdrawals. Even though any funds transferred via bank card are always secure, as they’re automatically withdrawn to the same card; funds transferred via bank transfer are vulnerable to attacks for financial gain, as we need the user to supply a bank account where funds should be returned.
  2. Staff access. The Seedrs staff handling operations are also potential targets, both for financial gain and business disruption.


We had to decide whether to make 2fa mandatory or optional. In the delicate balance of considerations for both security and usability, we decided to set it as optional for users (while heavily recommending it), and mandatory for Seedrs staff. Users who haven’t enabled 2fa need to confirm their password when attempting a privileged action — this doesn’t protect against compromised passwords, but may protect against attackers gaining access to a device with a session left open.

The second decision to be made was on delivery of the codes: SMS vs app (such as Google Authenticator) vs both. Here we wanted to support both while making sure our users always have a fallback to SMS. That way, in case someone loses their smartphone but are able to get a replacement SIM card, they’re not locked out. The way we made it work is:

  1. Require SMS authentication when enabling 2fa, and after that is set up
  2. Show the ability to switch to app for code delivery.

The final choice was on what 2fa should protect. Some sites will ask for it immediately on signing in; while others only ask for it at the moment of the particular action or actions which should be protected. We opted for the latter — this way users are also protected against leaving a session open; and there’s nothing too serious an attacker can do that cannot be easily undone, other than the points of attack identified above plus the privacy breach.

Choice of providers

We were divided between using Authy versus rolling TOTP to generate our own codes. The big advantage for Authy is that it’s an all in one package for what we wanted to implement. The integration work seems to be very straightforward, requiring less development effort when comparing to the alternative; and finally the encryption used by Authy seems to be stronger than what is done in TOTP, although TOTP is already considered secure. The main disadvantage Authy brings is vendor lock-in.

All things considered, because we are averse to vendor lock-in, we decided to go with TOTP instead, with Twilio to send the codes via SMS. The initial implementation entailed a larger effort, but since then the monthly running costs are a bit cheaper. Most importantly, we’re not tied to any single provider — we can easily move to another SMS gateway if we want to. Our users also have the option of accessing those verification codes via authenticator apps that support the TOTP algorithm.


We’ve used the active_model_otp and twilio gems. We’ve then modelled two authentication strategies to protect those actions such as withdrawals, that we have deemed as “privileged actions”:

  1. Two-factor authentication: Asking for the 2fa code when the user tries to execute a privileged action.
  2. Password: For users who haven’t enabled 2fa, their password is required instead.

Separately, we’ve implemented two policies to deal with how the authorisation is kept:

  1. A time-based policy where users who perform 2fa authentication will not need to do so again for a set period of time; and
  2. A user session policy where users only need to authenticate once.

The now

So far this solution has been working well for our case, with only a couple of minor issues: Sometimes the SMS messages take a long time to be delivered, we’re not sure if this is due to Twilio or just the network. Also, setting up a vanity caller ID with Twilio seems to be harder than expected — we haven’t gotten around to it yet.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.