It’s been two years now since we started to work on our Rolodex app, we’ve gone a long way and in this post, I’d like to share what we’ve learned on security and privacy practices to protect our customers.
Why security matter
As a company, our role is to make our customers happy and to build a long-lasting relationship built on trust. Our customers entrust us with their data and expect us to take great care of it.
We have to make sure that we build our software in such a way as to protect our customer’s data and privacy.
This is why privacy practices and security are so important, but why is it so often neglected?
Why security is often neglected
If you’ve read Nassim Taleb’s Black Swan, you will understand why I’m mentioning it here. If not, let me explain it in two sentences. One of our human biases is to underestimate the impact and significance of rare events.
Startup founders worry more about their servers going down than they worry about getting hacked because even though the latter is much more significant, the former happens more often.
On top of that, security is usually less of a problem in the early days because a small company with no customers is not a really interesting target for hackers.
Here are security practices that we’ve learned to love for teams, DevOps and developers.
Security practices for teams
Force 2FA everywhere
Especially now that the world is remote, it is harder to enforce security practices such as 2FA but you have to push through.
Github and Heroku are where I would start to look as unauthorised access to those apps could be fatal to your company.
Using a password manager such as 1Password is also highly recommended. If money is a problem, their family plan should do for a small company.
Not everyone needs access to everything. For example, does your lead frontend engineer need access to the production database? No. Does your lead designer need admin access to Stripe? No.
There is a sweet spot between security and convenience here. With 2FA enabled, it is relatively secure to give your team members access to the apps they need to do their job properly but do question this every few months and reduce accesses when they’re not required.
Security practice for DevOps
Before rage-quitting, let me explain.
We started by using AWS as we had received some startup credits through Pioneer. While it was great not to have to pay anything for our servers and database, the default settings for literally everything in AWS are wrong for small companies.
As a simple example, by default, an Elastic Beanstalk server runs only on
http and you have to spend a few hours setting up a custom domain and generating a certificate for it.
Setting up server logging as well is so complex we never managed to get it done. We were downloading the last 100 logs every time our server crashed and trying to figure out what was going on. Additionally, setting up rotating keys is a week’s work on AWS and these are just a few examples…
Basically, because the default settings in AWS are not good, you will miss things and have an insecure infrastructure.
Now, why Heroku? There are many alternatives such as Render or Zeet. We tried them both and Heroku is definitely the most mature product. While it’s much more expensive at a production level, it’s definitely worth the cost. For example, rotating database keys come by default, out of the box in Heroku. So does database caching and clean logging via LogDNA.
You can also expect Heroku to be much safer and performant simply because they’ve been doing this for years.
Set up automatically rotating keys
In the early times, we did not bother with automatically rotating keys/passwords and instead simply thought we would update them ourselves from time to time. This turned out to be wrong. No one updated the keys and worse, some staging keys were the same in production.
When thinking about security, you need to make sure that the default is the best. You should not need human action to keep your system secure. As explained before, the best way to implement rotating keys is using Heroku in my perspective.
You should use rotating keys to sign JWT tokens, for database credentials and to encrypt data in your database at least. This Heroku add-on will help.
Set up great logging that you will look at
With LogDna for example, you can easily set up views that show you the logs of specific apps that contain specific keywords. You can then set up Slack alerts when a new line with the keyword
error is logged.
I also recommend Sentry which will notify you of errors that happen in your code and make it easy to understand where the error was caused.
Logging is also very useful to log database accesses. If anyone tries to connect, you will know and be able to react fast.
Security practices for your developers
Monolithic architecture is better than micro-services
We initially started with this vision of having many small different services, distributed across many servers. The idea that the app would keep working even if one service went down sounded exciting but it was in hindsight a terrible idea, here is why: complexity is never worth it.
Your app should only have two repositories: one for the frontend and one for the backend. You should only have one backend API and one worker, nothing more.
Having one big server means that if something fails, you will know it! Which is exactly what you need in the early stages.
Use single sign-on or password-less login
We initially let customers sign-up by sending a link to their email inbox. This is fine from a security perspective but not a great user experience as the customers has to wait for the email to arrive.
Letting customers log in with their Google, Github, LinkedIn or Twitter account is a much better solution from a user experience perspective as well as from a security perspective.
Do not offer username/password login options! There are too many ways for this to go wrong. There are too many ways to wrongly implement this that it simply is not worth it. Facebook notoriously stored the passwords of millions of users in plain text.
Restrict resources by users
By default, an app needs to use the user’s auth token (jwt tokens usually) to identify them and show them their data. This is usually done by code like this:
In this python example, an endpoint returns the customers contacts and identifies the user’s
id using his/her jwt token.
But for more advanced endpoints that take in JSON data, for example, there might be a way to identify the user based on the data in the JSON as well. This is very dangerous as a backend developer might simply use the data in the JSON file to authenticate the user instead of using the JWT token.
For example, a mistaken developer could simply write the function that returns a specific contact like this:
This would be a massive mistake since any user could access any contact by its id! Instead, the function needs to look like this:
return get_contact(contact_id, user_id)
…and inside the function, we will check that the contact belongs indeed to that user.
Use environment variables
In the very early days, we used to hardcode some environment variables. Besides this being a massive security risk, it poses long term security issues if some legacy code gets accessed that contains an environment variable you haven’t updated since.
The right way to think about it is: “If we open-sourced our app today, would we be fine?”. You want to always be able to answer yes to this question.
Have a staging/production environment
In the first few months, we did not have a development environment, instead, everything was either local or in production.
Don’t do this. Setup a staging environment, even if you have to pay for an extra database and server.
It sounds like such a stupid idea in hindsight but at the time, saving a few bucks seemed like the right decision.
We’ve only been in this game for two years and still have many things to learn. We hope that this was useful!