I went down the rabbithole of using next-auth (now authjs) for a recent project. Having used Passport.js [1] for Oauth2 the last time I was doing node.js ~3 years ago, I found this library to have many footguns as comments/answers on SO and Github.
Seems like many people are trying to shoehorn their codebase [2] (!!) to make it work with the way the library manages sign-in flow, redirects, cookies, logout, etc. [3]
These were solved problems in the MEAN stack era with middlewares, but now that Next.js/react is the trend, people are doing everything they can to make it work - from relaxing security configs, to stashing things in the JWT just so some callback can get an additional piece of data [4].
The nextjs team for the longest time seemed openly hostile to server-side features in the GitHub issues. We ended up using the custom server feature to essentially bypass nextjs entirely so we could adequately do things like server-side auth in an explicit way using existing proven middleware. Next auth was inadequate for us mostly because of the limitations of nextjs itself. Nextjs 13.1 just added actual server-side middleware with full control of requests/responses so hopefully things will improve. I haven't fully investigated it yet but I'm hoping we can rip out all of our custom server stuff and replace it with middleware now.
> Nextjs 13.1 just added actual server-side middleware
the middleware has been available for awhile, they just added a few "advanced" features it looks like.
The problem with their middleware is that it's based on their edge runtime. Which is pretty much very basic web APIs and nothing more. Unlike Express/Koa, you do not have the full node API and cannot do things like read files from the filesystem. It's a total unnecessary clusterfuck just so Vercel can get you on their cloud services. Every single day I work with Next.js I wish I had Express and a decent router.
I like fastify more than express due to the great opinions (json schema, hooks etc). I use fastify + the fastify-nextjs plugin. It works great and lets you expose custom request decorations from node.js to next.js.
The only drawback is the slow startup time of next.js, which becomes really annoying with huge next.js projects. But for smaller projects, fastify-nextjs is fine.
Same. I found this example [1] particularly helpful, although I don't know how good this [2] library it uses is. Overall, I've seen multiple OSS projects [3] that try to support a missing functionality in Next.js seem to just give up trying to keep up with their breaking changes.
We are using nextjs for everything it can do, while bypassing it for the things it can't. With 13.1 that shouldn't be necessary because it is now more capable.
Agreed. This library is so opinionated that it more or less becomes useless. Youre way better off using iron-session or just go all the way and use a provider like Auth0, etc.
I work at Stytch, a company that provides an authentication API
> Seems like many people are trying to shoehorn their codebase...
This is something we're always thinking about in our product; write API first and flexibly enough so developers don't have to do cartwheels to use our product.
If you ever need to jump into authentication in Node again, give us a look!
> Seems like many people are trying to shoehorn their codebase...
Full Disclosure: I work at WunderGraph
But I think you should take a look at WunderGraph. It's vendor agnostic and allows you to choose a authentication provider that will work with your codebase.
I've used this lib in a few MVPs and it was easy to use if you do things their way (which wasn't that big of a deal for an MVP).
> The functionality provided for credentials based authentication is intentionally limited to discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords.
I have to admit while as a dev I sympathize with this stance, as an end-user I really hate it. I pay for a pwd manager and feel much better about using a user+pwd combo. I get that oauth is more secure in general but somehow I feel uneasy to give access (even limited) to my personal gmail or github.
Agreed. I'm a bit more privacy-focused than most but oauth + 3rd party seems like solution that should solve a specific problem or enfranchise a specific group of users and always have a password-based alternative. It's probably futile, but I'm just not particularly enamored with the idea of openly inviting all of those big companies and their greedy telemetry vacuums into every little last corner of my life.
What about a passwordless / magic link to your email? I've found that to be the best option as a dev/user, since no passwords necessary, no lock-in ("which OAuth2 provider did I use for this account, Google, Apple, GitHub, or Facebook?"), and it works decently well on all devices.
I agree, however check out the Checker Plus extension for Gmail (if you have Gmail). I never open up Gmail anymore, I can access all my emails through the extension which pops up a customized email window.
On the phone, for example with Slack, it has a button like "Open email" which I click, and then it opens the email, then I click the link which takes me back to the app, works pretty well.
I forgot the exact issue but if I recall it correctly, pwdless sessions are meant to be short lived sessions. I equally dislike reauthenticating my sessions every day
Why would they be short-lived? Just send a refresh and access token like normal authentication, the only thing you're changing is whether the user authenticates from a password versus a link which has a unique identifier, they're both looked up from the database / in-memory cache all the same.
I’ve been using this library extensively, and I highly recommend it. The switch from next-auth to authjs-core is a great move to make it framework agnostic.
That said a lot of the documentation is outdated and clearly react focused. I’ve encountered a lot of behavior that’s unexplained unless you dive deep into the code or GitHub issues. For anyone using this with SvelteKit, or just in general definitely contribute to the documentation if you can! It would help a lot!
It is definitely fairly easy to get started with and supports a wide range of authenticators (at least next-auth does, authjs not so many at the moment). The work on next-auth has been stopped in favour of this (also after some issues and rollback [1]).
One thing I was pleasantly surprised by was how easy it is too rollout email authentication via magic links and how relatively good it looks/behaves out of the box [2]. All you need is to add SMTP url and add the config.
It is also worth noting that it doesn't work for nextjs yet.
I'm waiting for a documentation PR to be merged at this moment. My personal experience with the library has been the same. However the lacking documentation SERIOUSLY hurts it, it's very easy to use if you already know what to do because you read the code, but if you went through that effort you could use just about anything else. Hopefully the team behind steps up their documentation game, authjs is something I have been waiting for a long long time and I overall like the decisions made by the team till now.
I don't have trust in the competence of original author. One example, the Credentials provider is mostly broken, and there's no working example at all ! All i want is simply authenticate with my database, no rocket science.
I've been using this library as well and I find that this isn't completely true. There are a few examples, but you have to pull and stitch from a few places. The documentation has been improving over the course of the last few months.
Is it just me or could the sales pitch use some work? What is this? Why use it? Elevator pitch, that is. Calling it “authentication for the web” is way too generic.
It's actually generic "authentication for the web" which is why it comes off as generic. They started off with being authentication for NextJS and it was definitely helpful for a quick auth implementation. Now it's being opened up to any web framework which is kind of a cool evolution.
It sounds like it's similar to Passport.js, which let's you add support for 3rd-party auth systems by just supplying API credentials instead of implementing an OAuth etc flow yourself.
It still leaves a lot of authn/authz work up to you, but it simplifies putting a "log in with Google" button on your site.
You could do it with a custom provider. You basically can define your own flow and inside that use the passkeys.
I plan on doing it this way in the product that i am working on, but to save time I use a parallel auth system that just plug and plays with react and then I do some user mapping in my backend (ugly haha but for now is good)
This looks like a library for social auth /various authentication providers.
WebAuthN/passkeys would have to be implemented on the provider side, not the client, so highly unlikely that this is planned unless their authentication server documentation was too well hidden for me.
This is indeed a good library. Well done. An enormous amount of work. For those looking for an out of the box freemium option, Clerk.dev is fantastic. (I'm just a customer. No association or interest otherwise.)
Then you'd have to use Svelte which I dislike over React, not the least of which is templating. After having used Vue and Angular before switching to React, you couldn't pay me to go back to logic in an HTML DSL. I can't look at v-for or ngIf again.
As someone who dislikes Vue and Angular but loves Solid and Svelte, I understand your reservations but I have to ask- have you ever built something non-trivial with Svelte? The list of DX improvements using TS in a framework like Svelte that gets out of your way without a labyrinth of gotchas and framework specific footguns and api patterns born through a decade of tech debt is nothing to scoff at.
This year I ported a quite complex web app I had in React and TypeScript, into Vue, Angular, Solid, and Svelte, just to see if I were missing anything given the new changes in the frontend ecosystem. I did not find that TypeScript helped more in Svelte than in React, particularly.
Happy to see the team moving in this direction. I've used NextAuth extensively and although it has some sharp edges, I feel like those sharp edges aren't unfixable. The documentation has been improving over the past year. I hope that having to adapt to more web frameworks will actually enhance the flexibility of AuthJS and leave few cases to have to shoehorn a solution.
I also wondered why the database schema is so bastardized with camel case and snake case column names mixed. Likely legacy, but still messes with the OCD.
Where do you see that? Constants are normally written with all caps and underscore while normal variables use camelcase. That's the convention most JS codebases use.
I believe it's mostly because next-auth was the previous package name, which is still in use, so it needs to get migrated over to @auth/next or similar.
A little, you could imagine this being a framework and passport being a collection of libraries and less of a rigid framework. I've worked with both and found passport/express to allow a lot more freedom than NextJS/NextAuth. But, NextJS/NextAuth was definitely more seamless to put together a simple POC.
I’ll hijack to ask a question that’s been on my mind…
I’ve heard people say “don’t write your own auth” but I’ve also spent a LOT of time hacking auth libraries to fit custom designs, custom flows, etc. And building tooling to integrate libraries with other infra: middleware, test mocks, etc.
My question is this: If you have a full time dev team maintaining an application, is it really that much slower in the long term to maintain your own auth flow?
I’ve basically given up on UI frameworks like Material UI because of this issue… if you’re building something customized, and you have full time devs to support it, the “time savings” of using a library like that is, in my opinion, a scam.
You feel like you’re getting a lot for free because the stuff that works out of the box is very visible. But the months and months of cumulative time spent dealing with the complexity that comes from customization and extension is invisible, because it’s just bugs you fix, or tasks that take a week instead of a day.
My team lost probably a dev month this quarter to react-select because of this. I’ve seen the same happen with Popper, react-table, and dozens of other supposedly helpful libraries.
I’m curious what other folks think about auth specifically though. Does auth fit into this pattern, or is it something that you really can meaningfully outsource to a library?
It depends on what context you're operating in. The reality is that most people don't fully understand authentication / authorization properly so they often mess up. When you have a small team of engineers that are spread very thin, it might be better to delegate this responsibility. If you have the time and resources to study the topic in depth and implement it properly then it's fine. It's just not that interesting of an area since the space for innovation and creativity is limited, and since the major problems have already been reliably solved by others at best you end up with an equivalent outcome and at worst you end up with security issues.
If you're operating within an enterprise context, Keycloak [0] is pretty massive but provides comprehensive coverage for all authN and authZ needs, and it's open source.
Back when I first started studying this topic I found that reading through a lot of NIST guidelines was helpful. I'd recommend at least browsing through SP 800-63-3 [1], SP 800-63A, SP 800-63B, SP 800-63C to get a good idea of the domain. Admittedly, this might be a lot of overkill for your application and needs.
Speaking from experience, what’s actually tricky about building your own auth isn’t the core functionality - there are lots of examples online - but rather all the things around authentication and user management that people don’t consider at first, but eventually become necessary when you go to production:
- password reset flows
- validating & cleaning inputs
- supporting multiple SSO methods for same account
- disconnecting or switching SSO methods
- handling duplicate accounts
- handling users who lose access to their login method
Don't listen to anyone saying "Don't write your own X". These things are not that hard once you filter out all the useless abstractions added by various libraries trying to give you a turn key solution. I suppose many just feel safe leaving responsibility in someone else's hands. I have never looked back after fighting passport.js some 10 years ago - being completely free to make you auth flow exactly how you need it is liberating! Now once you've done that a few times you are also in a much better position to decide if it might actually make sense to use a library or some auth provider for any future solutions.
I agree with your point in general, but I think passport.js and its ecosystem of plugins are very helpful. It’s lightweight and customizable for however you want to fetch and serialize sessions, and there are tons of examples online. Do you need it? No, but it makes it easier to standardize your login methods. A non-engineer on our team was able to build “login with linkedin” in less than a day just by following other examples in our codebase which is pretty powerful.
One thing about doing your own auth is you end up doing a lot of it. Integrations and expansion means more flows and schemes to do by hand. IMO setting up or buying a stable identity provider with all the bells and whistles is the way to go long term. Then you do your own client if you want but when you need to pull in another app they only need to work with your identity provider and not your code.
Build vs buy is a huge, complicated topic and it's more than just a technical conversation. A whole dev team is likely way more expensive than a SaaS for almost anything. You may think you can build it as a one-time cost and then maintenance will be cheap, but that's a trap. Every bug, every feature request is now on your queue and has to get prioritized against features that are actually core to your business. If you're the manager, how are you supposed to manage the risk of losing the team members who remember how it was built? You may think that hashing passwords is a solved problem so this isn't hard and that's also a trap. Building a fully-baked authentication platform is way more than just hashing passwords. You need all the UX flows for passwords, Oauth, email/sms recovery. Integration to your CRM or CDP. Obviously it's all solvable and possible, but why would you?
You can put the same logic behind loads of other enterprise software platforms. Building something like a CRM or Email management platform probably seems pretty easy, but if you look at what the SaaS products offer these days it's a mountain of features out of the box for a simple price tag. It's far, far less risky to buy than build.
Auth has been the target of a lot of SaaS and marketing. It's a menial simple task nobody wants to spend time on.
Even if you don't have a huge team I'd rather handroll over using third parties.
The mental effort of thinking about some other 3rd party developers or service is not worth it. I can write an auth system in under 100 lines and be on my way to ship features.
Social logins are the only annoying thing as they all use slightly different profile fields.
If you have issues with scale, scale vertically until it doesn't work anymore. By that point you'll likely have plenty of money to throw around for a registration team to worry about.
I’ve been in this situation before. I had a full dev team, we had a security architect assigned to us. We needed to do a pen test regardless of it we used a framework or wrote it from scratch.
Our requirements were quite unique and so writing custom code but aligning to an OIDC style flow was a good choice for us.
In the end it was a good choice and much easier than forcing an auth framework to do something it wasn’t designed to do.
We wanted to deploy into AWS lamba (using nextjs) with the main login flow being outside the app, then token validation being in a edge lambda. This decoupled the app from the authentication , we didn’t need any any authorisation.
If you use high level abstractions like authjs, you better use the happy path or it becomes a time sink. I think for authentication and authorization, use single purpose, well established libraries like bcrypt and passport.js and write the glue logic yourself. Sign up flow and authentication are too important to make concessions on.
I always understood the "dont write your own auth" as a much more low-level understanding of "auth", such as not writing your own password hashing (use bcrypt), not reinventing the wheel (dont design your own cookies / sessions, use a library, or do it like a library does it).
Ive never understood it as "dont make an api call to some auth yourself", because really, theres no problem with that (in my mind).
I've used things like django-allauth before and it's honestly not that hard to implement OAuth flows yourself and then you don't have to learn the ins and outs of someone else's API (i.e., allauth's numerous config settings).
Not sure this applies to Auth.js, but with hosted offerings, it's really hard to migrate off. Like how do you get the passwords into your system without forcing everyone to reset them?
> Like how do you get the passwords into your system without forcing everyone to reset them?
Disclosure: I work for an auth provider, FusionAuth.
I've written a number of migration guides and some hosted auth providers allow you to export password hashes (usually through a support ticket/out of band process; it's sensitive data). Others do not.
Definitely worth asking. It's your data, you should be able to get it.
Here are the ones I know that allow you to get your hands on password hashes:
* Auth0 (you have to be on a paid plan)
* Firebase
* FusionAuth (my employer, you get the whole encrypted database export)
Here are the ones that don't:
* Amazon Cognito
* Azure AD B2C
Once you have the hashes, it's a matter of ensuring that you can implement the same hashing algorithm so that the same user password entered into both systems ends up creating the same hash. Not rocket science, but sometimes, depending on the intricacies of the algorithm, can require a bit of spelunking. For instance, I was working on a keycloak migration guide and while both systems use Salted PBKDF2 with SHA-256, one used a 512-bit derived key and the other used a 256-bit derived key. I had to dig in a bit to figure that out.
It all depends on the degree of customization you do on your products, but if my team or I had to code all the auth or UI components by hand, I would quit the profession.
I speak from experience when I say that if there's something I like less than customizing MaterialUI component behavior it's working with a mangled custom-made replacement with worse semantics and functionality.
Disclosure: I work for FusionAuth, an auth provider.
I think of it like this: is the login and registration experience going to be a differentiator for your application? If so, how? Get real clear on the business value of a unique login experience (do you need to support special protocols? unique flows? pixel perfect UX control? multiple different user stores?).
If it isn't going to be a differentiator, then use a commercial or OSS solution that meets your needs, using the usual feature, standards and stability checkboxes.
If it is going to be a differentiator, evaluate the same options, but consider building as well. Much has been written about how devs underestimate the effort to build something ( https://www.google.com/search?hl=en&q=devs%20undersetimate%2... ), but I hear you, sometimes people underestimate how much effort can go into customizing something. Include, as best as you can, the features that are going to be 'differentiated', and see if customization or build from scratch is going to be more effort. (Spikes are helpful here, but to be honest, a lot of this pain is going to depend on future requirements that, being in the future, will be hard to foresee.)
If your auth flows are simple and easy to build, they'll probably be simple to customize. And if they are complex to customize, they'll probably be complex to build and support. TANSTAAFL.
You also want to think about maintenance. This of course cuts both ways. If you own the auth code, you control it and don't have to worry about breakages out of your control ( just like having your own C compiler: https://www.joelonsoftware.com/2001/10/14/in-defense-of-not-... ), but now you are responsible when a new feature or bugfix is needed, and it falls into your existing planning process. If you outsource this functionality, you still need to do integration testing, but you hopefully won't have to redo the work. (And you have a vendor to blame/hit over the head, which may or may not be helpful.)
Another way to put it is: should you build your own data storage system? Or use an RDBMS? Neither answer is always true, but for most applications, I'd lean towards the latter. I think the same is true with auth. It's a well understood space, with a lot of good solutions; finding one and using it will probably be the best option.
Seems like many people are trying to shoehorn their codebase [2] (!!) to make it work with the way the library manages sign-in flow, redirects, cookies, logout, etc. [3]
These were solved problems in the MEAN stack era with middlewares, but now that Next.js/react is the trend, people are doing everything they can to make it work - from relaxing security configs, to stashing things in the JWT just so some callback can get an additional piece of data [4].
[1] https://github.com/jaredhanson/passport
[2] https://github.com/nextauthjs/next-auth/issues/600#issuecomm...
[3] https://stackoverflow.com/questions/tagged/next-auth?sort=Mo...
[4] https://stackoverflow.com/questions/64576733/where-and-how-t...
EDIT: more links in case it helps the authors improve DX