A JWT can include claims - that's the difference: JWTs are a bit more complicated data structure out of the box. You can do authN and authZ in one go.
You can do it all via individual browser cookies but it will be complicated. However you can dump session cookies to a database and then you can do claims locally on the server and use that cookie to tie it all together.
So I think you can do it either way.
JWTs are mutually authenticated (shared secret) but cookies are not.
Everything can include "claims". Claims are just fields in a JSON object.
If you're using your own token format which is based on Libsodium's secret box, you can just do `secretbox_seal(secret_key, json_encode(claims))`. It's a no-brainer one liner. You can even use MessagePack or protocol buffers instead of JSON and save a little bit on the token size.
JWT might do other things for you, like standardizing how to deal with key rotation (using the "kid" claim and JWKs discovery urls), or tying a bearer token to a PoP structure (DPoP), but that's all about standardization. And as a standard JWT is too flexible and ambiguous. There are better proposed standards out there, and for most of the thing JWT is used for (non-interoperable access tokens) it's an overkill.
You can do it all via individual browser cookies but it will be complicated. However you can dump session cookies to a database and then you can do claims locally on the server and use that cookie to tie it all together.
So I think you can do it either way.
JWTs are mutually authenticated (shared secret) but cookies are not.