Ask HN: How many of you are rolling your own auth?
There's several auth-as-a-service providers on the market right now (Auth0,Firebase,etc). (By auth I mean, all the systems that facilitate password hashing and user authentication, along with SSO integrations.) Their common marketing argument, don't reinvent the wheel, and if you try, you'll probably get it wrong. This is pretty compelling as getting auth "perfect" seems to require some decent research and understanding. However, when viewing the customer pages of some of these providers, I don't see a ton of companies that I'm familiar with. Out of all the sites and tech tools I use, are they rolling their own auth, all of them? Once a site gets big, does it just get too hard to scale or adapt to the third-party provider?
88 comments
[ 3.4 ms ] story [ 136 ms ] threadOr too expensive compared to doing it yourself.
There's also a difference between rolling your own crypto and rolling your own auth. Leave the actual hash to experts and use NaCL's argon2 implementation. The salt+hash are just more user metadata that aren't super sensitive given modern password hashing algorithms.
Once upon a time, our company considered using Auth0.
Then we noticed that you have to pay nearly 25 cents per active user per month. I can possibly see the value for B2B, but if you're B2C good luck dealing with an extra 25 cents cut from your margins for every single user you have, paying or not.
On an internal work project, we outsource our authentication to Auth0, but stuff like user permissions handling and locking down our APIs has been something we keep pushing back on. We have this worry that, because it's security related, we'll "get it wrong" somehow if we do the authorization stuff ourselves. I've found it nigh on possible to find a nice third-party generic user-permissions web service though. Do such things exist? Libraries that don't have super tight integration with frameworks like Django and Flask? Stuff oriented towards a microservices architecture?
I've been thinking about building something along these lines. It would be path based, with 4 roles: Reader, Writer, Manager, Owner. So readers for example could GET, writers could POST/PATCH/DELETE/etc, managers could modify readers/writers, and owners can modify managers.
So for example you could call auth-server.com/add-reader/path/of/interest with a manager token.
Verification of credentials could be done with JWTs that contain a list of paths the user has access to. So any particular app could be completely decoupled from the auth server.
Is this at all the type of thing you're talking about?
Plus for some things you want to invalidate based on how recently they have interacted (IE bank logs you out if you haven't clicked/typed anything for 5 minutes) and you can't represent that without server state.
I don't necessarily disagree, but I'm going to push back a bit for the sake of discussion. I think it's worth questioning why JWTs should only be for authentication. Size is the only compelling argument I'm aware of, and I think there's a lot of cases where it wouldn't be a problem in practice. Would have to be measured.
What would you think of providing 2 interfaces to the auth server. You could return an authorized token like I mentioned, or you could return a token which is only authenticated. Then the app server ("client" in oauth parlance) would make a request directly to the auth server to get the actual authorization token.
So app developers could choose whether to accept authorized tokens, or only authenticated tokens if they want more control over security/token size.
> Plus for some things you want to invalidate based on how recently they have interacted (IE bank logs you out if you haven't clicked/typed anything for 5 minutes) and you can't represent that without server state.
I don't see a problem here. I'm fine with the app server having state. In this case it would simply mark that token as invalid.
I think it's best practice not to trust client data. It seems like a potential fail point that might even allow privilege escalation depending on developer configuration (and we are talking about rolling your own auth, so that's a possible concern). You have to store it on the server anyway (so the system can change it asynchronously without the client being online or display a user's permissions to an admin, etc), why duplicate it when you can check it on the server?
It also seems like a source of increased bandwidth costs, which is maybe what you meant by size. Even if it doesn't cost you, increasing the bandwidth a client needs for an app isn't user friendly, especially for mobile.
This should apply x100 for security related matters. I feel people only seem to bring this up with regards to hash functions and encryption but I think this should always encompass auth as well.
I use Firebase for auth. It saves development time, there's no user limit on the free tier even and it's battle tested.
There are limits https://firebase.google.com/docs/auth/limits on free tier and on paid tiers too.
Edit: These tools can be self-hosted and integrated to provide out-of-the-box auth functionality to your application.
* The ORY ecosystem of tools [1]
* Gluu [2]
* Keycloak [3]
[1]: https://www.ory.sh/docs/next/ecosystem/overview
[2]: https://www.gluu.org
[3]: https://www.keycloak.org
Well, I use npm's bcrypt + postgres DB of users (on an expressjs server, where I use knexjs for easy DB connection & use of JS logic in DB work).
1. Simply salt their PW when they create their account (which is simply a record in the DB) and store the salted version in the DB.
2. When they log in, salt the pw they entered during login and compare it with the salted one in the DB.
It's not tough.
Is it insanely secure? Nah, I am sure I am missing pieces involved in server and app security. But insanely stringent security doesn't matter for my purposes and use cases.
Password salting... About 4 lines of code: https://www.npmjs.com/package/bcrypt
Storing salted password in DB... another few more lines of code depending on what you use.
I'd say you can do this in under 10 lines of code.
However, that is of course excluding the underlying system and other logic which hosts this process of salting, storing, and then on login, comparing.
...However, if the question is really more like "How many of you are rolling your own auth which works in a high traffic, highly exposed situation, where expert levels of security are necessary?" then you'll likely encounter a different pattern of answers.
For password reset, you just create a record with a unique token and send an email that links back to the app with the unique token in the url.
Email verification is basically the same: send an email with a link that identifies the user and hit the server with the unique token when that page loads.
I mean, you could take something as facile as the sha1 of the current microtime, and a random concatenation of the user's data from the user table and that would already require so much access that figuring out the token wouldn't even be your biggest problem.
I once collected a $3k bug bounty over this. Python's use of Mersenne Twister in the lib/random module should not be used for token generation. Mersenne twister uses a relatively small state space and is fully deterministic (it never re-seeds or mixes in new entropy). If you get a couple sequential random values you can reconstruct that state space and predict all future values. I.E. request a password reset 10x in a row and examine the tokens in the emails.
Please only use secure random number generators when creating security related tokens.
Example blog post: https://know.bishopfox.com/blog/2014/08/untwisting-mersenne-...
Edit: never mind, your article named it as a good choice
Other flaws exist like abysmally poor key spaces. If your prng has a period of 64k and the reset links are generated deterministically from the prng, you're going to have a bad time.
The sha1 of microsecond+userdata is interesting. It has the potential to work well, but it's easy to get wrong. Latency measurements, the framework you're using, and other pieces of information can reduce microsecond timings to a few bits of entropy (e.g. there are modern systems that can only measure time aligned to 15millisecond boundaries). Once you take out the PII (most of which the attacker has access to already, so it isn't buying additional entropy), in systems I've seen there isn't that much real entropy in user state (sometimes under 12 bits even with tens of millions of users), and users who haven't interacted with your system much will have much less. If your system is closed source you might buy some security through obscurity, but that never lasts, and the underlying crypto is _probably_ flimsy at best.
It wouldn't take that much effort to go through my claims and find special cases where the strategies would work well, find workarounds for the attacks mentioned, and whatnot. That isn't really the point though. What matters is that getting this right is hard, and even a system which looks good enough might have subtle flaws that render its security all but useless. Maybe if we went back and forth enough we'd find all the problems, but there are already battle-tested solutions that are almost certainly better than anything we're going to come up with here, and in any application where security matters, ignoring those drop-in solutions is probably the wrong choice.
That said, it might very well be the case that having a certain percentage of user accounts compromised is an acceptable trade-off (or even desirable? could you then charge people to monitor their accounts for suspicious activity à la Equifax?). I think that's a choice that should be made consciously though, not as an afterthought arising from a broken security model.
Which is how some people seem to approach security advice... "either it's up to my ideal standard, or it's a completely idiotic implementation that will surely be hacked in a fortnight."
You seem to have some balance and I applaud that. Security is a balancing act between the level of security, development and maintenance difficulty, and user experience and you have to negotiate an acceptable level that at least exceeds the bare minimum of security required.
This is why, for example, most sites will confirm/deny the existence of an account for a given email (provided by an attacker) during password reset flow. There simply aren’t great prepackaged solutions for the part beyond “hash it and put it in the db”.
This is also why sites that do 2FA frequently fall prey to the “steal the SIM and you can reset the password with no other factor” attack so commonly.
Need high performance swoole can practically compete with go, but you could still put now server intensive services in go or rust.
On most apps I find myself writing my own register / login / password reset logic, and just using Auth::login($user); to get the user logged in and give them a session.
From that point onwards though, Laravel does a fantastic job and Passport is a god send.
As for eMail verification, you create and store a verification ID when the account gets made (I use a Guid), and it is this that the system dumps into the verification link that gets sent to the end user. Every time the username gets changed, that Guid also gets changed (along with the verified flag getting cleared) so another verification link can get sent out that is unique. Reusing a link is always bad, because you want to ensure the user goes after the most recent link.
TIL that Dunning and Kruger were
inspired by McArthur Wheeler, a Pittsburgh man who attempted to rob a bank while his face was covered in lemon juice. Wheeler had learned that lemon juice could be used as "invisible ink" (that is, the old childhood experiment of making the juice appear when heated); he therefore got the idea that unheated lemon juice would render his facial features unrecognizable or "invisible."
https://rationalwiki.org/wiki/Dunning-Kruger_effect
//edit: not sure bcrypt does NOT do this by default.
That's bad advice. BCrypt and pbkdf2 have a work-factor that accomplishes the same thing built in. The work-factor is added to the start of the resultant hash allowing different work-factors to be intermingled. This allows you to scale your work factor up over time as hardware power increases (or to mitigate weaknesses found in the underlying algorithm).
So, no, don't hack your own version of the work-factor instead of using bcrypt's solution. Your hack-version won't be standard compliant (between different BCrypt libs) or self-documenting. It also isn't increasing security, but is adding more potential sources of bugs.
So it's bad advice to use a work factor and instead you recommend using a work factor? Uh yeah, still thanks for pointing out how that's called I guess? To clarify: I didn't say one should roll his own hashing, I just described the process; I simply don't assume the average HN reader to be an idiot and already use bcrypt or similar.
Edit: forgot to mention, parent comment also roughly described what his bcrypt is doing.
That said, those services (the good ones) tend to offer much more than a simple user database, for example:
* Login with Google/Facebook/Twitter/Github/...
* Login with IAM services like OpenID Connect / SAML (mostly for big companies)
* Two factor authentication, maybe with several token types (TOTP, SMS messages, recovery codes,...)
* Password recovery flow
* Rate limit the pace of password attempts, in order to prevent brute forcing
* Monitor incoming network traffic and block unusual behaviors
* Maybe implement a CAPTCHA or similar challenge for users with unusual behavior
* Make sure the storage of all secrets is actually properly protected (password hash, password recovery auth tokens, issued OAuth tokens, etc.)
* Audit logs
If you implement auth(z) by yourself, most likely if you're going to have to sacrifice on some of these points, otherwise you would spend all your time here and never work on your project!
Also, at this point I think haveibeenpwned makes it very clear that having a password database in 2020 is a huge liability, and any options that move passwords out of your own databases and into someone else's problem seems like the smart idea.
(I'm not affiliated with Auth0, but I have used them for projects where Azure AD didn't make sense, as I do fear owning passwords in my database in 2020.)
Edit: Equivalently any SAAS sucks since they may in the future raise their prices is your argument.
This is a very real argument though? Reliance of a core part of your saas on another company, who is liable to change prices at any time, is a real business risk. The Google Maps API pricing change is a big example.
My instincts and experience tell me that oauth is more complicated than it needs to be, but I'm still too inexperienced to say for sure. I'm in the early stages of a deep dive of web auth. The frustrating part of learning oauth is most of the articles/videos explain the steps of the flows, but they don't explain why each step exists. ie what are all the security holes that would exist if we skipped this step?
Anyone know any good oauth books/resources that build rationale from first principles?
[0] https://patchbay.pub/pro.html
Also not a good resource, but acceptable: Pluralsight. There is one straight up OAuth course to go over all the basics and then quite a few language/framework specific ones, e.g. how to implement OAuth in Node/ASP.NET/etc. The OAuth course was dry but had some decent information - but I did quit halfway through it because of IdentityServer, so take that with a grain of salt.
And yes, it sure does feel more complicated than it has any right to be. There's a good read here https://hueniverse.com/oauth-2-0-and-the-road-to-hell-8eec45... by the once-lead-author.
I really do recommend checking out IdentityServer4 though, unless you're implementing this specifically to learn / have fun / etc. And if you don't care for the Microsoft ecosystem, I've heard nice things about Hydra https://github.com/ory/hydra which is a similar Go offering.
I totally agree there are so many resources about implementation, and honestly it's pretty straight forward. My guess is that because of this people don't think to question it and simply assume it's all necessary. And maybe it is, but in my experience necessity is often tied to specific assumptions that may not be true for a specific use case.
With oauth in particular I suspect a lot of the details are tied to the assumption that you have to do a full redirect in order to authenticate. But my emauth.io service uses email over a back-channel to authenticate, so the user can stay on the app page while they verify their identity. So at the very least you don't have to worry about redirect hijacking.
I’m a lot more likely to use an app where I can use my existing log in
But more practically, I work for a B2B company where we integrate with their IDP (Okta, Active Directory, etc.) a third party service/app makes that a lot more feasible.
1. Not all users are comfortable with trusting a third party in order to use my app. Some people actively mistrust and avoid megacorps like Google and FB. They should only have to trust you, not a chain of companies.
2. Writing code to integrate numerous external API calls is a comparable effort to just doing it all custom.
3. The UX of jumping to a third party dialog to log in and then back in to the original app is jarring. "What just happened?"
4. It introduces more potential points of failure with less control over being able to deal with such issues. If the third party services or APIs fail, or a user can't access those domains for some reason, tough luck.
5. Someone else owning your app's user records is troublesome. You still need to have your own user records for things like session state, roles and authorization. You have to keep your user records synchronized with theirs.
6. Users sometimes do not want to link their accounts on other services to your app - they prefer separate identities. When your Google account's avatar appears in an app that has nothing to do with Google it can be annoying, or even perceived as a privacy violation.
7. User authentication involves a standardized, conventional set of practices and code that are well known and not hard to implement.
8. If the third party service you put at the core of your architecture decided to shut you down or compete with you, or they shut down themselves, you'd be in big trouble.
9. Sooner or later you will pay for this service. The more successful your app is, the more you will pay. If you do it yourself, there's no cost beyond your regular hosting costs.
10. KISS - Keep It Simple, Silly. Don't add unnecessary dependencies and complexity.
Every time I ask somebody about this, they recommend slightly different practices or else insist that you can’t use a checklist approach. I partially rolled my own auth as an experiment to see what they meant and it seemed not too difficult, but I worry I missed things and hesitate to use it for production.
(Still used libraries though like bcrypt and a session encryption library)
Can you provide a link to the source? I can't seem to find it on their GitHub. They only appear to provide install scripts that grab the compiled java application off Google Storage.
Also, you can self-host, but you must agree to a somewhat restrictive license[1] upon install. As I read it, you can only self-host on equipment you own or operate (I don't own or operate EC2), can't deploy for anyone else (no use in consulting), are agreeing to random audits with 30-day notice, and can only use for "Licensee's internal business purposes" (so only auth for internal applications, not public-facing?). Even if these things aren't true, I don't feel comfortable that I understand what I can actually use this for with their current license.
[1]: https://fusionauth.io/license
I'd recommend using something like Fireabase as the front end, that issues JWTs etc., but the actual user database to still yours. Firebase calls this custom-auth. That way, you benefit from their client libraries in multiple languages (Js, Swift, Kotlin, ...), and also from the reliability and security of their server side, but since the actual DB is yours, you have the option of moving to another provider, like Cognito, if need be
Magic links that embed the OTP inside of a clickable URL have a couple added threat scenarios, mostly revolving around the tenuous connection between app launching URL custom schema and OS/App Store/User Permissions. In some, but not all cases, OS/App Stores make it hard or unlikely for a third party to intercept those URLs, but not always impossible. There's no real central registry for the custom schema between App Stores, for one example.
The successor to custom schemas "Universal Links" (pushed by PWA standards among other things) have an interesting mitigation in requiring the links to be HTTPS, with TLS-verifiable metadata that promises that link is to a domain that the App author clearly controls before passing things on to the App. It's not a perfect mitigation, but a useful one as part of a larger defense in depth, depending on your application/site's threat model.
:)