Building Okayar (Part 4): Authentication and Authorization via Firebase Auth

The scariest part of building a web app for me was building proper authentication (authn) and authorization (authz). First, auth is a huge bridge between the front-end and back-end, and I had no prior experience implementing auth from the front-end side. Second, I’d never used a third-party auth provider like Firebase. And third, I really don’t want to be that guy that got his tiny little pet project broken into and had to send his 8 users a “sorry I leaked your data please don’t sue me” email.

So, I did a lot of research and worked hard to make sure I implemented auth correctly! This article will cover my auth implementation on both front-end and back-end, and will hopefully help out beginner standalone-web-app developers who happen upon this guide.

For those new to Okayar, this is the last of a 4-part mini-series on how I built Okayar from the ground up. You can find at these links the first article on the back-end in Go Lambdas, second article on AWS infrastructure through Serverless & Terraform, and third article on the UI in ReactJS.

Quick aside: Authentication vs Authorization

Just a few quick words for those unfamiliar with the distinction! Authentication (authn) is the way we handle a user’s login. When a user logs in with their credentials, we are verifying who that user is, and then giving that user a token with which they can take actions on the website. Authorization (authz) is the way we decide what a user has access to and what information we should return to them. So, using the token from the (authn) stage, we will return only the data relevant to the user and prevent them from seeing screens they shouldn’t be seeing.

How I chose Firebase Auth

My very scientific way of choosing an auth system was googling ”best auth provider web app react reddit”, reading reddit threads, and narrowing down my options to Auth0 and Firebase Auth.

Auth0 and Firebase Auth come with similar features, at least when it comes to what’s important for small projects like Okayar. They both offer social media login (facebook, google, etc), both have UIs that are easy to plug into any front-end, and both provide easy-to-use SDKs for use by either the front-end or back-end.

Auth0’s advantage seems to be in the professionalism and enterprise-oriented nature of its services. Where Firebase draws its advantage is pricing and ease-of-use.

Firebase’s pricing is pretty much unbeatable– it’s free for unlimited users, except for phone-based auth, which becomes paid after 10k users. For my app, I had no reason to offer phone auth, making this an easy choice. Auth0 wouldn’t have been a bad choice either, but in software we learn quickly that we should be avoiding “ticking time bombs”. Just take a look at this post, where Auth0 became so expensive for a company that they migrated all 6 million users to Firebase Auth. Auth0 is free for up to 7k users, then quickly becomes expensive after that.

Firebase also provides an entry into the Google Cloud, if I wanted to use it. Google makes it super quick to sign up and use, which is great for app devs like me who are trying to set something up with minimum hassle.

I will skip past the Firebase console setup in the UI — it’s fairly straightforward and Google does a good job of walking you through the steps. Let’s get to the code!

Authentication (Authn)

Firebase login via React

First and foremost, I needed to set up the way users will log into my app and actually reach the authentication screen. I hinted at an important file in Part 3 of this series involving auth, which is where I’ll start:

Here, the bulk of my app (OkayarHome) is located at the / path, which I’ve described as a PrivateRoute. The idea is that a user needs to be properly authenticated to access the / path. If they are not, then they get bounced to the /login path. Let’s take a look at how I accomplish this by first investigating the PrivateRoute component.

To track the auth and user session in this whole app, we use a React hook called useContext. A “context” is meant for data that should be available throughout the entire app. It’s kind of a circumvention for the usual props-based paradigm in React, so it should be used rather sparingly. In Okayar, we only use it for tracking the logged in user. Other recommended uses are themes (dark vs night mode) or a selected language. Basically, it should be used only if its value is really necessary in almost every component, and if it is not updated too often.

So above, if the user is logged in, we allow them to continue along their actions. If they are not logged in, then they get sent via Redirect to /login.

This leads us to the home of our auth setup, where we create the currentUser context:

On line 6 we create the context variable mentioned earlier, which is used throughout the application. Then, on line 8, we create our AuthProvider, which (in app.tsx above) wraps the entire application. This AuthProvider component is huge for us, as it checks for the status of the login, and then either returns to the user (a) an empty home page or (b) the children of the AuthProvider component with the user passed to it as currentUser.

There’s one more file that is essential to the authentication setup — login.tsx. This file creates the UI for login:

First, let’s look at lines 18–23. These 6 lines carry out a feature you’re very familiar with– if you hit a login screen when already logged in, you should be automatically redirected to your home page.

Next, on line 25, we describe the uiConfig, which determines what login options we show the user. This is a required prop for StyledFirebaseAuth, which we use to render our auth screen. Finally, we pass firebase.auth() into the StyledFirebaseAuth. This firebase.auth() uses environment variables described elsewhere to have the UI interact with my actual firebase auth instance in Google Cloud.

And that, in short, is how you get to a login screen like https://app.okayar.com/login!

Authorization (Authz)

Passing auth tokens from the front-end

For a user to make requests involving storing, updating, or retrieving data, they should be properly authorized. In today’s apps, that means the user is able to pass a token that validates their identity and access. Thankfully, this is not very hard to do.

From the above section on Authn, you saw how we find the currentUser from any given component: const {currentUser} = React.useContext(AuthContext).

This currentUser, then, is passed into any API request. And using the firebase library, I’m able to get a token to pass into the request that the back-end can use to authorize the user:

This token appended as the Authorization header is then used by the back-end to validate the user and decide what data to return, allow edits on, or allow creation of.

Authorization in the back-end

Now we rewind all the way to Part 1 of this mini-series: the back-end built in Go lambdas. I need to take the token that the front-end is sending with each request and use it to (a) make sure they’re a real user, (b) check that their token isn’t expired or invalid for any reason, and (c) only allow them to create, edit, or read content that belongs to them.

In that spirit, checking the user’s auth token is the very first thing we do on any request to our back-end. Let’s take a look at one of my function entry points:

Lines 17–24 in the above function are the key to authentication. We call a helper function called GetCurrentUser, and then either throw a server error if there is a 500 or a 401 error if the user’s token is invalid. Let’s not leave GetCurrentUser as a black box and take a look at that too:

These commands look a little confusing, so let’s go through them 1-by-1.

In line 17, we load up some environment variables which we need to talk to firebase. It took me a long time to figure out how to get environment variables into a Go lambda. Let me try and help the next person trying to figure that out — right before compiling the binary, you want to use a command like go-bindata -o helpers/files.go -pkg helpers files. This takes every file in the files/ directory and turns it into a Go file called files.go that gets compiled into the final binary. So, on line 17, we are extracting those environment variables into data.

Lines 22, 23, and 28 are taking those environment variables and initializing the connection with Firebase. And finally, on line 34, we carry out the most important step. We send the token that the user gave us to Firebase, which responds to us with information on that user, including their UID, email, and of course whether or not the token is valid.

And that’s it! We’ve verified that the token the user gave us is valid, and we’ve extracted the user’s email and UID. This information will be passed along to the rest of the app’s logic so that we can store & retrieve all the right data.

Reflections

Okayar was a journey in proving to myself that I could really build web apps. I started this project in roughly March 2020, when I was ~9 months into my full-time career. At the time I’d really only done bits and pieces of different projects and had plenty of imposter syndrome. I think this might be common among new-grads; it’s hard to feel “real” when your weekly assignments are just mostly features, bug-fixes, and additions to giant existing applications. It isn’t until you dig deep into the end-to-end of an application (front-end, back-end, infra, auth) that you can understand and appreciate the complexity each one presents.

I hope that for someone looking to build a project from scratch this series will have been helpful! And for anyone who’s been reading along, thank you. As always, feel free to reach out or comment for any feedback or advice :)

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store