The dictionary defines ‘authentication’ as “the process or action of proving or showing something to be true, genuine, or valid.” So true. Since the dawn of time, people have been wondering how to authenticate requests.
I recently found myself where, I assume, my ancestors, and my ancestors’ ancestors have all found themselves: at the juncture of needing authentication, and not quite knowing how to do it. And this is where our journey begins today.
The Call to Adventure
Recently I’ve been working on a personal project: an app for organizing your skincare products and tracking them. I actually built it already, last year, but it was honestly such a rough first draft that I, the creator and also 100% of the user base, abandoned it a week after launch. At the beginning of the year, I spent some time rethinking the build, and decided pretty early on that, it’s 2018, I need to build users into my app. I can’t realistically be asking users to “fork and clone and then open terminal and start up the server and then start adding all your products its SUPER easy and user-friendly”.
How hard can it be? I asked myself, and set up a quick little card on my project board and figured I’d get to it when I get to it, and it’ll be like, a couple weeks’ worth of work at most? Users are everywhere! You can’t go anywhere these days without signing up for something before even using it, and if Jim can build a b2b vegetable sharing startup app in his basement and get users and auth built into THAT, surely I, a Live Employed Engineer, can figure it out. I walked into this ticket with the confidence and swagger of Lindsay Lohan onstage in Mykonos. Did I know anything, even a single thing, about adding users and auth to an app going into this? No. Did I believe I could whip something up pretty quickly? You bet, pal.
The Struggle, of the Crossing of the Threshold
You might, at this point, be wondering what stack I am working with. Thanks for asking! Get ready to regret it. The app’s stack is, basically, a Frankenstein of some things I’m already comfortable using (Rails, Vue) so I could get moving quickly, plus some new things I’d been wanting to learn (GraphQL! The API of the future!), because this app was also a sneaky way to learn some things that had been on my list for awhile. Multitasking, baby! It’s how we feel alive in 2018! So, the stack is: Rails on the backend, Vue on the frontend, GraphQL for the API, Apollo for GraphQL on the frontend, and the graphql-ruby gem on the backend. My philosophy when choosing the stack was “why not, honestly”.
To start, I did some research, and gathered criteria for the app’s users and auth system: specifically, I needed to be able to authenticate API requests, attribute data to a specific user, have an easy (sign in via google/social would be nice) sign in, and do all this with the lowest lift possible because I’ve also still got an entire app to build. I was less concerned with security here, because we have a current WAU rate of 0. The main concern here was just being able to see only your personal skincare data.
Googling ‘users auth rails how’ will pretty immediately direct you to the gem Devise. It’s great! Has a lot of built in stuff, everyone uses it, lots of 5 star ratings, etc. But because it assumes a REST API setup, and the whole point of GraphQL is that it isn’t REST, it wasn’t going to work. So it was back to researching. I looked into a lot of things, particularly for things that mentioned both Rails and Vue and GraphQL in the documentation, but it turns out I was in a prison of my own making. Nobody else, it seems, has willingly chosen this nightmare stack. Who could have guessed.
I looked into a lot of other things, too, but the important thing for you to know is that I didn’t choose them. I spent a lot of time reading up on DIY tutorials but decided against them, because they would show you mostly how to get there, then say, “but no one in their right mind would do this in production! So figure something else out.” This is useful if you’re building an app for, I suppose, no one, or your enemies. At this point I had learned a valuable lesson, which is that users and auth is a lot of work, and that maybe it was time to start looking for something that handled most of the internals for me. This led me to Firebase, a Google-backed solution, which does a lot of things, but particularly handles a TON of auth methods for you, has built-in support for signing in through various apps, and a bunch of nice lil convenience functions so you can do things like get the current user. Incredible.
As it turns out, adding Firebase was the easiest this project would ever get! In less than a day, I was able to get it up and running so users were able to sign up, login, and logout, which are three absolute bare minimum types of functionality users expect of your app in 2018. This, conveniently, meant it was working in time to show at the end of our company hack week, and where no one was impressed with it, because it was a tiny two (2) field form where you put an email and password in and then could sign in like any of the other 300 hundred apps you visit every day, but it meant a lot to me.
This was also when I paused to read up on JSON Web Tokens. It’s a neat little token that carries a bunch of user data around so that you, An Owner of An App, can use it. Frontend Masters has a great course on it, how it works, and its advantages over session-based auth. This is what Firebase uses, and in this house, what Firebase says goes. And this is where things got messy.
The Tests of Will, Abilities, or Endurance
At this point, users could log in, log out, and sign up to their heart’s content, but none of this was linked to the backend. It was a bit like having fancy, expensive curtains covering up a gigantic pit. Cool, if you like pits, I guess. I did a lot of research around using JWTs (street slang for JSON Web Tokens) on the backend, and found a ton of solutions for building your own JWT auth system. However, these all assumed you wanted to generate and authenticate JWTs on the backend first, not the other way around. This was unfortunate for me because Firebase was doing all of the generating and auth already and, and really, all I wanted was a way to attach the user Firebase had created and verified to the users in the backend, so I could return to a user the data they’d entered.
Looking back and knowing what I know now, the solution is pretty simple, and I could just save the token provided by Firebase to the db and use that token to pass the right data back and forth. However, I still a. didn’t fully understand how JWTs work, and what using them meant, b. had a pretty fuzzy grasp on what auth meant in general, and what you needed to *do* to actually securely authenticate a user, and c. was going through this all for the first time, and was confused about how all of this fit together in the 15 layers of hell that was my stack. I really cannot stress enough how many different auth systems I’d built into the app at this point. I’d started and had to abandon about 15 different implementations, for various reasons, most of which can be summed up with, “not meant for production code, more illustrative than anything” or “way too complicated for what I needed”. I wanted to quit!! So badly. At this point I’ve been working on this stupid thing for months, making barely any progress, all the exciting work for the project on pause until this was solved, my spirit was dying, my boyfriend was losing patience with me yelling about users and auth at every chance I got. In short, I was being a nightmare.
Finally, I decided finally to just google to see if there was a firebase gem for rails. Why I didn’t do this sooner, I’m not sure, but I would guess it’s because I spent a lot of time thinking I needed something that worked with GraphQL too, and was spending a lot of time searching for apollo/graphql solutions for auth and ignoring rails. But! There was a gem, and it did exactly what I needed it to, which is take in the Firebase token passed in from the frontend, and decode it in Rails.
Well. I got this gem hooked right up, followed the docs, and did everything *chefs kiss* perfectly. But it didn’t work. There was a bug where, I could see that the token was correctly being passed into the controller, and the gem was doing everything it should be doing, but the token wasn’t decoding. Confusingly, putting a byebug in there and running the decode commands individually by hand decoded it perfectly. So there was something weird happening where the token wouldn’t decode during a normal request, but if you paused and went in, it worked fine. And this began a deep dive into source code.
The Ordeal and its Reward
Success was so close, I could almost taste it. Whereas the past few months had been a cycle of agonizingly slow progress → start over → repeat, I was now making Real Live Progress in meaningful pieces now, and this bug was the only thing standing in the way of me and that sweet, sweet decoded token. This was the final battle, and I was really ready to fix it and then move the h*ck on.
The journey to solve this bug began where most journeys to solve a bug with an obscure gem begin: a quick visit to the gem’s issues page to see if anyone else has had your problem, only to realize no one has, because only three people, in the history of ever, have used it. Next up is to look at the source code, where I really realized I was on my own, because every single method for authenticating the token rescue’d out without surfacing any errors. If it fails, it just returns nil, and thank you for coming to my ted talk on how to disappoint your users.
Next came the wave of confidence that hits you after looking at some source code and thinking, “ya i could do that”. And I really wanted to be able to dive in and add my own error handlers so I could see where this thing was failing. So I did what any sane person would do, and copied the entire gem into my own repo so I could debug it line by line. It’s only a few files, so it’s not too bad, and it’s working pretty quickly, and it’s only 2am on a weeknight so I’ve got like plenty of time to fix this before the sun comes up. Next I set up a tangled web of `raise`s and `byebug`s throughout the app, and finally see it: a `JWT:InvalidIatError`. If you google this, it has little information beyond people frantically copy and pasting that exact error message into stack overflow accompanied by a ‘pls help’. So next I find myself deep in the ruby-jwt gem source code, which the firebase gem relies on, to find the source of this error to find the line where this is happening.
As it turns out! Apparently tokens can be given a timestamp that is slightly in the future, and the check in the ruby-jwt gem checks specifically to make sure the JWT was issued prior to the time of check, as shown here: `raise(JWT::InvalidIatError, 'Invalid iat') if !iat.is_a?(Numeric) || iat.to_f > Time.now.to_f` And apparently this can happen for a variety of reasons, including but not limited to, your computer’s time being out of sync, the origin setting the IAT being out of sync, divine intervention. Alright then.
After some further reading, it turns out that this is an issue a LOT of people have run into and yelled about on the internet. I am going to be honest with you here: I just removed the call to check the issued at time of the token. Am I proud? Honestly, yeah. I tracked down the source of the issue, and my app doesn’t have millions of users (as previously mentioned, it has 0 users), so I think we will all live if I delete this one check. In the real world, in an actual production app, would I have found a way to fix it? Absolutely! There was something about being able to pass in a leeway time to allow for some of these discrepancies in issued at times that, had it not been 3am, I probably would have had more patience to look into.
Anyway! Today, September 19, year of our lord 2018, the app is able to authenticate users from frontend to backend. ‘Hey but you only described logging in, what about everything else? And why’d you mention GraphQL if you never used it in this tutorial?’ We-he-hell, it looks like we are out of time. Stay tuned for my next 16,000 word essay!
Have you too found yourself the victim of a particularly terrible stack, and need to add users? Here’s a list of resources that helped me, and that may help you, too. Don’t wait, click today: