![Search icon](https://hackernoon.imgix.net/search-new.png?w=19&h=19)
No, it's not. It's boring, red-tapey, a solved problem... but don't call it hard as a blanket statement.
I'm "I've used MD5 to hash passwords in PHP" years old. Sure, it was a horrible idea, even back in 2012. But, back then, I don't remember considering auth "hard." It was a pretty straightforward ordeal by itself - get an email or a username, get a password, hash it (with MD5, as "god intended"), and if you were especially security conscious, [salt] the password. Store all of that somewhere, usually in a database. Ta-da, signup done.
Nowadays, the narrative has changed. "Auth is hard" feels like an ever-present narrative that lies a HackerNews or Reddit click away. But is it really? IMO, The truth is, auth isn’t hard to build - anyone can learn it (and everyone in this line of work should learn the basics). The real challenge lies in the extras: MFA, user management, password resets, each of the hundreds of OAuth providers, and account merges from different providers. It’s death by a thousand cuts. Since auth is a solved problem, reinventing the wheel isn’t the best use of your time. But that doesn't mean "auth is hard" as a blanket statement is correct or even close to correct. You should experiment, understand the basics, and build from there. The complexity only grows with the scale (or potential scale) of what you're creating.
So, how hard can auth really be? Let's dig in.
Picking up where I left the story about PHP and md5, building a login functionality followed a similar set of steps; Get an email and a password, check for the existence of the email in your storage, hash the password together with the salt stored for that email, compare the resulting hash with the one stored in the database, and if it all works out fine, set a cookie via setcookie
(we're still in PHP land here - not that the overall logic was too different in other ecosystems).
Signing out was even simpler - just invalidate the cookie on the server, and you're done. If the server doesn't see a cookie with the next request, you're not logged in. So doing authenticated routes was also a simple ordeal overall. Things could get hairy when it came to permissions, but more often than not, with the apps I had to build, we had just admins and users. Which was something you could simply store together with the user record or in a permissions table if you ever needed to expand the amount of roles you had for your app.
Full disclosure—I work for SuperTokens. This piece, however, is borne out of personal frustration over an omnipresent narrative about how hard auth is as a blanket statement. In other words, I'm not trying to "sell you my thing." Use whatever you like.
To get to where we are today, we'll start at the beginning... Surprising, I know. We can probably agree that these components are enough to make an email/password + social login PoC:
Going with Express and Passport, since we're not going to reinventing wheels, we arrive at exactly 150 lines of very, very dull and repetitive code: https://github.com/supertokens/auth-express/blob/master/index.mjs. The next section will be surface-level explainer of what's going on in the code, so feel free to skip ahead if you're already familiar with the concepts. The express app is a PoC anyway.
Let's quickly dissect it:
The way I see it, there are two ways to approach this - start with the rendering and move on to the auth route or the other way around. Mostly by chance, I ended up being an FE-heavy pleb (I can still do SQL, in case you were wondering), so I'll start with the "rendering stuff on-screen" approach.
Since this is a PoC, we're not gonna go all React-fancy. Plain ol' SSR with ejs will do just fine: https://github.com/supertokens/auth-express/tree/master/views
Based on some passport.js examples, but simplified further, we need the following:
Some deps: npm i passport passport-local express-session
. Let's briefly go over each:
A place to store our users (the example linked above uses an in-memory array): https://github.com/supertokens/auth-express/blob/master/index.mjs#L13
Configuration for our passport instance and our LocalStrategy instance to handle incoming requests for user lookup: https://github.com/supertokens/auth-express/blob/master/index.mjs#L18
Initialize passport (https://github.com/supertokens/auth-express/blob/master/index.mjs#L60) and express-session (https://github.com/supertokens/auth-express/blob/master/index.mjs#L69).
Verbose, sure. Hard? I don't think so, at least not in the implement-it-as-a-toy sense. But we moved past using email/password combos a while ago. Let's inspect how hard it is to add a social provider on top of what we have.
For an example provider here, I decided to go with GitHub for a simple reason—if you decide to fully follow along, it's one of the easiest providers to get started with (looking at you, Google).
If you do decide to follow along fully, here's a link describing how to get those GitHub keys: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app Oh, and BTW, the ones in the repo aren't valid, in case you were worried ;)
First off, we need one more dependency, npm i passport-github2
. passport-github2 is an auth strategy for Passport, allowing us to integrate with GitHub's OAuth2 API.
Some handlers (https://github.com/supertokens/auth-express/blob/master/index.mjs#L122-L133) and configuration (https://github.com/supertokens/auth-express/blob/master/index.mjs#L29-L45) later, well, that's it. Complicated? Probably not. Red-tapey? You bet. Boring? Absolutely. Especially if you get to do it over and over again. It is a solved problem; reinventing wheels is often not the best use of one's time as we've established.
By now, we can probably agree that Auth isn't hard to build. Ergo, it's not this magical thing that only white-bearded wizards who speak the mystical language of JWTs can understand and implement.
No, in fact, I'd argue that as a developer, one should understand the bare basics of how auth works. And I often see a narrative that claims otherwise - something to the tune of "trust me, bro, we can handle that for you". And sure, I agree that for the most part, rolling your own auth is a waste of time. But it's not that hard to build, and it's certainly not a mystical thing. Where it truly becomes hairy is with everything surrounding auth and the user experience.
Consider this - in the example above, we have a working auth thing. Sort-of. But here's what it can't do (also mentioned in the article opener):
We can probably implement each of these. And on its own, each piece may be considered simple. But it adds up. So, it's not necessarily the implementation—it's maintaining it, being responsible for it, staying up to date with the standards, security breaches, and so on. Plus, a show of hands—how many of you like reading RfCs? I don't imagine I'd see many raised hands if we were on a meetup.
My point is that auth isn't easy, taken as a whole. Sure, we can easily cobble something together for a PoC, as we did above. But it's not magical, it's not impossible to understand, and please, please don't say that it is. That line of thinking (and marketing), IMO, is damaging to the industry as a whole.
So, the natural follow-up question would be - when should you roll your own?
...by all means. I'd even encourage it. You learn a lot by doing, so why not? If your indie/toy project or blog grows to have a considerable user base or following, switch it out to a service, a self-hosted solution, or something else. After all, you have a product at that point, and your time will undoubtedly be better spent building that product instead of maintaining auth.
Generally, if you're building products, don't roll your own auth. It's reinventing a very boring and red-tapey wheel. You have plenty of options to choose from. Plus, you are building something, right? Why are we even having this conversation if your product isn't auth?
Don't. Same reason as startups - but it certainly applies more here.
You can probably see where I'm going with this. "Auth is hard" is, I'd say, a marketing pitch when used as a blanket statement. You can understand auth, you can build it, but it's boring, it's not fun to maintain, and it's a problem that's solved. Thus, it can be considered a commodity—one that you can pick off the shelf in whichever flavor you choose (some options below).
For the ones that are into owning their stack (as yours truly is), you have plenty of options to choose from, too:
Auth libs
Auth servers
Storage + Auth
So, even if you're not into using third-party software for Auth, you can just pick an open-source one off the shelf, depending on your needs and preferences and roll with that.
My "big" takeaway is to avoid reinventing wheels, especially if it's a solved problem, as auth is. Get educated about said wheels, experiment with them, build a toy wheel, and understand it. But please, please, don't sell it as this impossibly hard thing to understand and build. Educate, don't gatekeep.