Building a Secure Application

Building a Secure Application

I realized a couple weeks ago that in all my time in the software industry, I had never implemented a full authentication model. I wanted to learn what it was like to build a modern single-page application that included security best practices. So I started a simple todo list project with the goal to learn various aspects of what it's like to build a full-scale, secure application.

Feel free to checkout the application at https://checkit.epicbits.dev! If you'd like to see the code, it's on Github.

In this post I will discuss the highlights of my learnings.

Architecture

I'll start off with a high-level architecture of the application. I've got a single computer at home (so please don't try to take it down, I'm sure you can) that is exposed to the public internet via an IP address, using a DNS provider to connect it to the epicbits.dev domain.

I have Caddy proxying requests from various sub-domains and URL paths to different services. I am serving the UI assets (written using React) from a separate Caddy server. The API server is written in Go and manages sessions and interacts with a MongoDB database for storing user data.

All of these services are running in Docker.

Login

First is the login experience. I did not want to deal with the host of other things that come with managing passwords – storing the password in a secure format, verifying the user's email address, password resets, multi-factor authentication, etc. So I decided to use social login via Google OpenID Connect. All this time, I thought it was referred to as OAuth, but instead is actually OpenID Connect – a protocol built on top of OAuth.

Account Access

Along the way, I discovered that some applications will gain more access to your provided login account than they should (and more than you want). So be careful about that consent screen! My application will only gain access to the most basic information – name, email address, language preference, and profile picture.

Authentication Flow

I also discovered that there are multiple authentication flow types and that some of them are now considered insecure. I used react-google-login and discovered that the default behavior uses the Implicit Grant Flow, which has been identified as a slightly insecure way of authenticating due to the access token being passed to the client. Unfortunately, the component appears to have a bug that does not allow the Authorized Code Flow – so I'll fix that at some point.

Logout

It's easy to provide a logout button for when a user explicitly wants to logout. However, what should happen if the user leaves the website open on their computer and comes back a few days later and opens it up? Typically, the user will get redirected to the login page.

WebSockets

In come WebSockets! We can avoid a polling model to determine if the user is still logged in. I added an endpoint to manage WebSocket connections for the lifetime of a user session. When the session expires, the server sends a message to let the client know to redirect the user to the login page to re-authenticate.

Session Management

Managing sessions was a new concept for me. I knew about it and I understood the concepts of a session, but not the implementation details. Once the user signs into the application, I create a session token on the API service and send that back for the browser to store it as a cookie, to be able to authenticate with subsequent requests.

There are a lot of security best practices for managing session tokens and I am following the OWASP guidelines.

Cross Site Request Forgery (CSRF)

After I got sessions working, I installed ZAP to see if I could get a user to do something they did not intend, such as delete all of their items! But my tests were not successful. I soon discovered that due to the SameSite cookie attribute (which I was using), it is no longer necessary (as long as you don't care about IE...) to implement traditional CSRF protection with an additional token stored in the web page content. How convenient!

Limits

I decided that it would be useful to apply limits before exposing the website to my colleagues at work. Being engineers, it is not uncommon for the first course of action to be – open up the console and run a never-ending for-loop to create items! I definitely couldn't afford that on my small home server. It's almost out of memory and doesn't have a lot of CPU capacity anyway.

Rate Limiting

My first course of action was to implement rate limiting on HTTP requests. I used the tollbooth library for convenience, but it looks like it is quite easy to implement on your own. The library uses an algorithm called Token Bucket, making so we don't have to store data for each user on the requests they are making.

Request Size Limit

I also limited the size of headers and request bodies at my proxy server with a simple config line in Caddy to 8000 bytes. I don't want someone DoSing my server with an enormous payload.

limits 8000

Data Limits

Next, I should probably do something about ensuring someone does not have billions of todo items. Is this application even useful if you have that much to do and aren't getting it done soon enough? I haven't seen this as much of a need after adding rate limiting because it will take a long time for someone to create this much data.

However, this is an important topic to discuss for large applications. When listing items for example, the endpoint should paginate the request and not just grab all the data out at once. This is inefficient for the browser to render all at once and takes longer to transfer large amounts of data over the network. And although I have rate limiting, it is possible for my server to run out of memory at some point...

oEmbed

Just for fun, I looked at what it was like to make a web link come up with some nice info and image using oEmbed. Chat and message applications use the framework to provide more info about a link.

Turns out it's super simple with the following in the <head> tag.

<meta property="og:url" content="https://checkit.epicbits.dev/"/>
<meta property="og:title" content="Get more done with Checkit!"/>
<meta property="og:description" content="Checkit is a simple example single-page application that features social login with Google, user session management, and database storage."/>
<meta property="og:site_name" content="Checkit"/>
<meta property="og:image" content="https://checkit.epicbits.dev/favicon.ico"/>

Conclusion

I still have some things I want to figure out, and I've definitely learned a lot with this side-project. Thank you for following along!