Hacker News new | past | comments | ask | show | jobs | submit login
API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies (wanago.io)
59 points by mwanago 8 months ago | hide | past | favorite | 63 comments

Nest.js is a great framework, with excellent documentation, but it can be quite heavy weight (from a coding and concepts perspective) and alienating to JavaScript devs without a background in C#/Java.

Its basically Spring/.Net MVC implemented in TypeScript. That makes it a great choice for .Net/Java developers who are transitioning to coding for Node.js, but for anyone without that background, it comes with a steep learning curve (Dependency Injection, Inversion Of Control, Aspect Oriented Programming... all the usual suspects of "enterprise" application development).

I'd say its positioned to become very popular (and you can see its trending strongly upwards on npmtrends.com) because so many enterprises are transitioning towards Node.js. Its like a cozy blanket for ex .Net/Java developers.

I always find it amusing that that Nest.js is a back-end framework, that was inspired by a front-end framework, which was inspired by a back-end framework. (Nest.js -> Angular -> Spring MVC). :-)

I just wanted to link to eropple's comment[0] as he provides a good counterpoint to the discussion of the merits of Nest.js

[0]: https://news.ycombinator.com/item?id=23302463

Strongly agree - as someone much more comfortable within the node ecosystem, there's several libraries I'd rather use in place of Nest's choices. However it makes a great starting point for people not aware of the other options and who need an opinionated bootstrap.

If anyone's wondering my current template is routing-controllers, class-validator, tssyringe, and sequelize-typescript.

Separate issue, but if anyone can suggest a different query builder / orm in typescript that'd be truly appreciated. Imo typeorm is too much of an abstraction and sequelize works but requires custom types ever since v5

Until yesterday, I would've voted Prisma. There was actually a Show HN post recently that debuted Zapatos: https://news.ycombinator.com/item?id=23273543. The top comment also mentions mammoth: https://github.com/Ff00ff/mammoth. Probably going to switch over to one of them as they're closer to raw to raw SQl and well typed.

I like MikroORM, but haven't yet used it in a production environment.

Class-validatdor puts you in a deep hole, though. Consider ajv; it's what Fastify uses under the hood. (Honestly, just consider Fastify. It's great.)

The dependency injection seems to miss the point of dependency injection though. I'd love to be proven wrong but, in the documentation, under the providers section [0], they seem to be wiring CatsService directly as a concrete implementation because, I guess, interfaces are erased in compile time. In C# such problems don't exist. Weird, as they also mention SOLID.

[0]: https://docs.nestjs.com/providers

Its true that, by default, you use a Class when wiring up the DI container because as you guessed, the interfaces are erased during transpilation.

You can still achieve the loose coupling of DI by using custom providers [0]. Basically, this approach is the full syntax for defining providers and gives you the most flexibility (inject based on Class or token, provide factories, simple values, class instances, etc).

[0]: https://docs.nestjs.com/fundamentals/custom-providers#custom...

Then why not use libraries like io-ts [0] to define interfaces, which survive the compilation, can test untyped objects against the given schema and could also be used as DI tokens. Still, depending on structural typing here feels like a hack, but I never did this so maybe someone with experience in a large codebase can chime in.

Anyway, IMHO, Typescript isn't designed for dependency injection in mind. Microsoft also has a DI library [1] but it feels like too much wiring to compensate for the lack of compiled interfaces.

[0]: https://github.com/gcanti/io-ts

[1]: https://github.com/Microsoft/tsyringe

I'd say that tsyringe is about the same level of "wiring" that one would see with a Nest.JS module. The major difference being it handles the implicit wiring a bit better. Nest.JS will freak out if you don't explicitly tell it what to do. (could be a good thing?)

I'm sorry, but I'm going to have to strongly disagree on your first point.

I've used Nest.js extensively over the past year or so and I find the documentation extremely lacking. Sure there might be a lot of hello-world style examples, but advanced or sometimes even basic usage is often not well documented and the actual API itself is completely undocumented (you might have an example of 1 way to call a function, but that's it).

You often have to piece together examples from various "Documentation" pages, which should really be called Examples, or dive deep into source code to find the list of functions or parameters that are available.

Another pain point is that I sometimes ran into extremely hard to diagnose issues with Nest. I had to add Swagger documentation to a project recently, which uses previous version of Nest.js (v6). It turns out that the latest version of @nestjs/swagger is not compatible with this version of Nest.js, but the docs don't mention which version you should install to get it working.

I installed the library as described in docs for Nest.js v6 and used their example to include it in the project. When I started the application, it didn't print any errors or really produce any useful output at all (besides all the DI logs and the fact that initialization was successful). Instead, the application simply fails to print "Application Started" and it just doesn't listen on a port.

I fixed that by comparing release dates of older @nestjs/swagger versions with release date of NestJS version that we were using. There's nothing to go on in cases like this and whenever something like this happens (multiple times over the past year for us), you can probably imagine how frustrating that is.

With TypeORM, we also frequently run into issues that haven't been addressed in years[1], missing functionality in their query builder[2] and strange behavior in general.

I commented about this previously[3], also I should point out that I appreciate the effort they are making, but I would recommend that anyone considering using Nest.js in production takes a hard look and really evaluates whether it supports all of their use cases. In our case, it looked really good on the surface, but once we got deeper into the project it started showing a lot of its warts. Expect to do a lot of babysitting with this framework if you venture outside of their narrowly documented use-cases.

[1] https://github.com/typeorm/typeorm/issues/2065

[2] https://github.com/typeorm/typeorm/issues/296

[3] https://news.ycombinator.com/item?id=21962492

This definitely takes me back to the early AngularJS days where the ONLY thing one could do was read the source. Any questions on forums would result in, "did you read the source???". I personally HATED reading the source for that project. It was like reading source code from someone who left your company in anger 5 years before you arrived.

Stateless JWT for auth is one of the worst trends to catch on in the past few years. Don’t do it – always check your tokens against a database. Otherwise, if someone compromises an account or gets their hands on a token, there’s basically nothing you can do to stop them.

Side note, the idea of JWT without verifying the token against a database, made me doubt what I was doing.

I wasted too many cycles trying to understand how it was secure while debating how much latency my database check was introducing on every single request compared to those simply using the JWT as-is.

It has made something I considered "settled" to be a time-sink.

They don't make a ton of sense until your public-facing services get kinda mesh-like. If it's all in one DB, including authorization and authentication data, and you never have one service requesting resources from other services on behalf of a user, they're probably overkill and regular sessions would be fine.

(Nb. I've not personally seen them used in a case for which they make sense, yet, but only in smaller projects because they're trendy)

I'm currently implementing auth with JWTs. I've settled on shortlived access tokens and long lived refresh tokens. The refresh tokens get validated against a blacklist. This way you get the best of both worlds, you minimize db access while at the same time have the ability to revoke tokens.

Another thing one must be aware of with JWTs is protecting your users against XSS and CSRF's. Make sure your shortlived token is stored on localStorage and the refresh one is stored in a cookie.

Why not store short lived token in a cookie as well?

Because then you can be vulnerable to csrf attack. For example if someone tricks you into clicking www.mysite.com/api/delete-account

CSRF attacks can be prevented using same-site policy with cookies.

That is true but it will not protect against all forms of CSRF, for example you'll be vulnerable if you have user generated content that's not sanitized properly. On the refresh_token cookie I have sameSite and httpOnly set.

Same story for any session token that isn't rotated on every request. And even then, if they get the latest one you're out of luck.

Unless you mean blacklisting tokens, then, yes, you do need to check your blacklist, though fanning that out to an edge cache should suffice and keep you from needing to hit your authentication service or database every request. But that's true for any long-lived session token.

The point is that with stateless authentication, you can’t revoke tokens. If a JWT gets compromise, you need to either wait for it to expire or log all users out by rotating the signing key.

With stateful authentication, solving this is trivial: just check your tokens/sessions/whatever against a centralized database every request.

Something that's come to light for me recently and which I'm trying to understand the reasoning for, is bringing OOP patterns like singletons, dependency injection, visitors, etc. over to a multi-paradigm language like JavaScript. My understanding is that these were all basically clever hacks for working FP and procedural concepts into a strictly OOP language (plain module-level state, closures, and plain functions/pattern-matching, respectively). But you can get the same advantages without jumping through any of those hoops in JS and the like. Are they getting brought over purely as a comfort-blanket to Java/C# devs or is there something I'm missing?

Never understood this either.

But when I see how TypeScript looks like JS+C# it feels like OOP people just can't help themselves

It really doesn't have to though. TypeScript can look like C# or it can look like Haskell, or it can be one in some places and the other in other places. This is my favorite property of [JT]S. You can simply use the best tool for the task at hand.

You may want to use Argon2id for password hashing instead of bcrypt, as it is more secure. See https://security.stackexchange.com/questions/193351/in-2018-...

Unless you are have a formal proof of your entire application plus operating system and enforce very long passwords, the choice between bcrypt and Argon2 isn't improving your security. Even if that describes your situation and custom bcrypt silicon becomes available, you can buy some and increase the work factor. Also if this describes your situation, trusting JWT is a huge risk. For any web application, your time is better spent closing run of that mill web vulnerabilities.

Why not use md5 then? You can always add more rounds.

I'd argue (though PHK himself disagrees) that yes, PHK MD5 crypt() is still an acceptable choice in 2020 for exactly that reason. You can add a bunch of rounds to defeat a reasonable attack, and it's unlikely this is your system's weakest defence. Should you build new green field systems intentionally with PHK MD5? No, no reason to. But you are kidding yourself if you believe that's the weak point in an existing system or in a compatibility mechanism.

Toy systems that just do MD5(MD5(password)) are screwed because they don't have any salt, but PHK MD5 uses salt so all the time-space tradeoff attacks are made irrelevant, bad guys will need to brute force each account they want to attack individually.

If which crypto hash to use is a major security consideration you are already Doing It Wrong™ because the only point of these systems is to store human memorable passwords, and human memorable passwords are hot garbage anyway. Either outsource the human authentication problem to somebody else or, if you are quite sure that's strategically unsound (e.g. you want to be Amazon or Apple with fingers in every pie) then require passwords not match PwnedPasswords and either mandate or at least strongly encourage a second factor, with at least TOTP and WebAuthn options offered.

But why not use it? It's more secure and easier to use because you don't have to generate a salt.

I haven't started using NestJS yet but am very impressed with the documentation. Here is it introducing its Websocket and GraphQL support: https://docs.nestjs.com/websockets/gateways https://docs.nestjs.com/graphql/quick-start

My team picked up NestJS as we've moved from Spring Boot/Java to Node Lambdas. The ability to use CDK + NestJS to deploy a single Lambda to handle a few endpoints is amazing. NestJS allows us to write code more in the Clean Architecture style without it becoming really heavy like we see with Spring. I've enjoyed it so much that I might use it for my person projects too (leaning towards Dart for one language across the stack).

Do you mean you lean towards using Dart for everything except for the backend?

I want to use Dart for all of it. Recently Dart has a compiled version. Another project integrates this with AWS Lambda. Might be nice.

Having cognitive dissonance between all of these very similarly named js frameworks. Next, Nuxt and Nest... is there some kind of weird inside joke that I’m not in on?

Nuxt is Next for Vue.js, so it does make sense. Next and Nest are unfortunate, always need to read it twice to be sure.

Thanks for the clarification.

I love NestJS. With backend specifically I always recommend the more barebones frameworks like Koa and Express to beginners so they can learn how all the moving pieces interact with each other. But once you already know that, it's so nice to use a framework that only needs you to tell it what to do (as opposed to how, like in Express). I use it for all my pet projects in node now :)

One of the things I took away from NestJS was the focus on dependency injection, even doing that with Express made it so much better to develop against.

It’s one thing I really hate about almost every tutorial or example that’s out there, they just pull in dependencies out of nowhere.

Nest.js + Angular on the frontend is a dream. Nest.js was modeled after Angular and they both use a similar basket of concepts/tools (dependency injection, decorators, first-class TS support). You can build a robust full stack app without context switching. They are both opinionated, ensuring a consistent and predictable structure.

If you go the Nest + Angular route, I recommend checking out NX monorepos, which adds another level of seamlessness. NX was created by former Google/Angular team members. One of my favorite features includes the ability to easily share libraries/models across the front end and backend. E.g., if you're designing an API, you can import the same model file to both the front end and back end, ensuring no divergence between both sides as the API is redesigned.

> One of my features includes the ability to easily share libraries/models across the front end and backend. E.g., if you're designing an API, you can import the same model file to both the front end and back end, ensuring no divergence between both sides as the API is redesigned.

To be fair this applies to any case where you have the same language on the front and back, which was one of the main motivations for Node in the first place

As far as sharing JS library code yes, but the baked in typescript support + nx CLI add a lot in terms of usability.

1. nx g @nrwl/workspace:lib data, 2. Modify the typed models/interfaces. 3. Import to backend + front end.

I'm a front end developer, so NestJS was a blessing for helping me write my API. It gave me a set of rules to follow and amazing utilities to make my life easier, such as exception filters [0]. One thing I admittedly struggle with is knowing when to create a new Nest Module versus a new service in an already existing module, and I do not think Nest provides such guidance on application architecture. However, I believe this is a result of my minimal back end experience, and shouldn't be held against the NestJS project. Overall, I am really enjoying it.

[0] https://docs.nestjs.com/exception-filters

> I admittedly struggle with is knowing when to create a new Nest Module versus a new service in an already existing module

I use the same logic as where to put a font end component. If I'm creating a component for the first time, it sits in the same folder as the parent component. If I need to use it with another component, I refactor it into a common folder. If I create a string transformation function, it exists in the same file that is using it. If I need to use it somewhere else, it gets refactored into utils/strings.js which is a starting place for creating a library.

It solves the problem of 'where does this go?' and 'where did I put it?' 95% of code that I need to find will be physically next to the code that uses it in a file system. If not it will be neatly organized into a common use directory.

For the few times I need to refactor functionality, at that point, I will invest time to make a clear and adaptable API input and output contract.

Woah, I was trying to look at the source of bcrypt.compare and ended up at http://bcrypt.compare/.

Apparently .compare TLD hit General Availability in 6 May 2020.

Yet another example of tld poison that makes universal search bars even less useful since anything with a period in it is now automatically a tld.

I think this is only if the unspaced period is in the first term. Having only recently moved to 100% omnibar from URL + Search bars I was momentarily annoyed by what you mention, but I soon realized it really doesn't come up very often because I'll typically use spaced terms and I can remember to put domain names (or inter-period'ed terms) after a first term.

Or, yet another example of how the universal search bars poisons the URL handling in order for someone to drive up usage of their search traffic. Two views of the same coin.

I've had the option to use the address bar as search set in Mozilla products for as long as it's been an option (so like 20 years?) and it's only in the last 2 or 3 years that it's become unreliable.

I just started a project in NestJS. Prior to this I was working on express for NodeJS backend where I had to do a lot of manual stuff and achieving dependency injection across the application was a pain. I'm impressed with NestJS so far. If I could go back in time and re-work the project, I'd choose NestJS. I've also worked with Rails and Spring boot. With NestJS I get the same ease and flexibility like in Rails and Spring boot.

Finally a great web application framework for NodeJS.

I've built a large application with Nest as the backend and gotta say, nothing but love for the framework. IMO it is the leader in typescript backend space.

Nest + Typeorm + Postgres + Apollo + NextJS for front end = best of many worlds

I read six or seven comments down before realizing that this isn't Next.js. Each time I noticed the "s" in someone's comment I assumed it was a typo. Not a great naming decision to be one letter off from something else that's closely related and in the same language.

I've spent the last eighteen months using NestJS. I can simultaneously call it a good step forward in the Node space and something I wouldn't use for a new project. They've done a lot of work to build a system that, so long as you stay within very narrow lines and don't ask too many questions, feels very cohesive and consistent. The unfortunate part is that it shakes apart at speed if you don't do both of those things.

People make a lot of hay about the NestJS dependency injection system, and my intuition is that a lot of it is that it's a novel concept to a lot of the folks running into it. If you come from a Spring or related background, however, I think it's pretty clear that NestJS has a pretty critical misunderstanding of dependency injection. Modules are hardwired together--`BarModule` requires `FooModule`rather than just declaring what they need and what they export and allowing the dependency resolver to figure it out regardless of what module they come from. As-is, you cannot replace a dependency coming from `FooModule` with one, say, from `Foo2Module` without changing every consumer. It's a high-boilerplate devolved service locator rather than dependency injection.

"Pretty critical misunderstanding" has become my default description of big parts of NestJS as I've gotten deeper into its use over the last year and a half. This description really does fit a lot of aspects of the system. It often feels as though the implementors read half a description of this or that, decided "that's a great idea!", and then implemented that half-a-description...poorly. The "microservices" concept (really just RPC, and not particularly play-nice-with-others RPC) is a good example, as is the really clunky access to WebSockets. CQRS is another prime example, as they've somehow implemented single-node CQRS, made it impossible to distribute, and declared victory and went home.

It is better to not provide official support for something if that official support is going to be so full-stop awful, because that official support will choke out attempts to innovate in the space because you've already taken the mindshare for the problem at hand. Which dovetails into the problem that will probably end up putting NestJS in the dustbin as soon as somebody else comes up with a competitor: the NestJS team seems to have what feels like at best an oblivious and at worst a rivalrous relationship with their community and with Node as a whole. Over the last year they've rolled out what read to me as lock-in, don't-play-nice-with-the-ecosystem-as-a-whole "features", like their poorly intentioned CLI "monorepo tooling rather than just using lerna for multi-modulestuff like the rest of the world. Also like the future-forecasted "Nest CLI plugins" for things like linting otherwise bonkers misbehaviors[0]. As somebody who's written a bunch of open-source modules for NestJS, I consistently get the feeling that Kamil would really prefer it if developers just shut up and took his stuff and made apps (maybe paying him along the way?), rather than building their own open-source stuff alongside it. I still have a very bad taste in my mouth from the way that they just stomped on the `nest-schedule` project by added a less-featureful but otherwise identical "cron-like" gizmo to NestJS core without so much as a nod to the community member who wrote the better one before they did. The idea seems to be that NestJS isn't a community, it's a single-source vendor, and in 2020 that's weird to me.

I've used a lot of NestJS but I can't really countenance using it for new projects, and I can't recommend it to others.

[0] - https://github.com/nestjs/nest/issues/3279

I don't have as much experience working with Nest.js as you do, but I can see some validity in some of the things you mention.

Nest.js tries to have "answers" for everything (i.e. CQRS, MQTT, Kafka, etc., etc., etc), but I do wonder how well implemented these answers are (vs. checking of a list of "completed" features.. Their documentation certainly looks impressive when it comes to breadth).

I think if you stick to the core concepts of MVC with some simple DI (and if you are building something that pushes the limits of that DI system, maybe its too big), Nest.js can take a team of developers new to Node.js and enable them to quickly build well structured applications (certainly with higher quality than the average express applications I've seen in the wild).

Do I think its the perfect swiss-army knife of Node.js server side development? No, but for a team of regular developers, I think it fills an important void in the Node.js landscape (the same void that was filled by Spring MVC in Java).

As with everything tech related, it all depends on the use case.

I don't disagree with anything you've said. But, that said, I feel like there's a brick wall there for NestJS users and I feel like the incentives of the NestJS core team are hard to ascertain. Uncertainty creates risk; if you're comfortable with that level of risk, that's great. I'm growing to not be comfortable with it.

If there was a roadmap and a set of principles for governance I'd feel much better about it, but I also have trouble seeing that ever actually happen.

> I do wonder how well implemented these answers are

Not well, tbh. "Microservices", as mentioned, are useless. CQRS is fundamentally misunderstood. Their OpenAPI 3 libraries (and I will cop to some bias here, my frustration with their OAS implementation got to the point where I wrote a better one[0]) are implemented in a developer-unfriendly way that's totally separated from validation so there's a ton of diplication of work. (And let's not even talk about validation! `class-validator` is a mess. "Pipes" are pretty silly as a concept. Fastify uses `ajv` under the hood; there's not a great reason why everybody else doesn't, too.)

As you say: checkmarks. A lot of checkmarks. But then you pick up the corner of the carpet and the subfloor's chewed through.

[0] - https://github.com/eropple/nestjs-openapi3

So what would you use for a new Node server project? I'm guessing, from one of your other comments, that Fastify would be part of the answer. Would you use an ORM? Any other pieces you can recommend for a typical database-backed server application? I'm just curious, since I've never done a non-trivial project with Node.

So I'm writing what I'd use right now. :) It's not yet quite done enough for public consumption but it's an app framework, rather than a web framework, and it expresses its HTTP layer through a facet that uses Fastify as its web server. It also includes an "event responder" facet for responding to events off of an event bus, a Redis queue, etc., and it's all wrapped up with a dependency injector that bears more similarities to a real one (dependency injection rather than module-welding service location) than to Angular's. The key structural difference to me is that there's a unified way for all of these systems to interrelate in a way that you see more with a mature framework like Spring Boot, over less mature tools like NestJS.

I'm agnostic as to ORM layers but both Objection and TypeORM are worthy of consideration. My project's DI container will play nicely with both--it supports dynamic scopes and so it's really easy to do something like "create a transaction scope and request a service from it, and that service automatically wires up transition-backed TypeORM repositories." Which is hard, hard sledding in existing systems.

I hope to open source it relatively soon.

I've been keepig an eye on nest.js for a while now. I haven't had a chnace to use it yet, but from the docs it looks like the closest thing to a rails-style framework in Node land. Albeit less mature/comprhensive than rails/django/laravel.

Another project in this vein in the JS ecosystem that's starting up right now is Redwood [0]. I'm optimistic, considering it's backed by Tom Preston Warner.

[0]: https://redwoodjs.com

Those magic functions for named routes though... I'm not sure such magic would do well with typescript

Please use a “password hash” to store your hashes. The best practice algorithm is Argon2.


Because you might not be able to program Elixir?

I feel like thats kind of a comparison of apples and oranges. Nest JS is a framework, Elixir is a programming language.

He probably meant Phoenix Elixir

Elixir is synonymous with Phoenix. If that is the only reason to choose using Phoenix over NestJS, the later is clearly the better choice.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact