The moment you have to look up the user object, you've lost the primary advantage of JWT, and might as well ditch it.

Depends on the system. If you use JWTs for authentication only, they still serve a purpose. Sessions also only serve as authentication, not authorization. Authorization is independent of the both systems, and it depends how you implement that.

There are systems where the authorization is done in the JWT too (i.e. scopes/permissions in the token) - in that case you are right.

It definitely violates DRY but if you keep passing the JWT down the call chain, you can do redundant permission checking in your business layer.

Now the reasonable response to the above is that this should be happening in a dedicated authn/z concern - and that is correct! But when paranoia is called for, it's not unreasonable to have redundant checks in logic where authz is critical.

> It definitely violates DRY

DRY conventionally refers to repeated sections of code, not redundant communications.

This. Totally defeats the purpose of having JWTs if you have to hit the DB or some service to validate the token every time.

It depends how you store and look things up. There are so many optimization opportunities and strategies to make the lookups fast that this is pretty much a non-issue in practice (e.g. deny list implemented as any combination of runtime or in-memory index, trie, or bloom filter). At the first invalid bit the lookup fails and the token is allowed to proceed to subsequent auth checks. Which should happen quickly for the vast majority. No need to head straight for the worst possible implementations, like whitelist disk lookups.