You can start with running your JSON data (or whatever serialization format that you choose) through NaCl/libsodium's crypto_secretbox() and you already get more than what JWT offers by default.
It still doesn't include everything you need of course: at the very least you'll still need to implement expiry validation and some token revocation mechanism. But you actually get more value out of the box than you get with a JWT library: your token content is encrypted, your using a solid crypto library and you don't have to mess around with any knob to secure your implementation.
It doesn't offer support for hot-swapping crypto algorithms, but for what it's worth, cryptographic agility is the root cause of many issues we had with TLS in the past[1].
If you end up needing both symmetric and asymmetric cryptography in your tokens, you better treat them as two different types of tokens, because they usually are. I think tptacek already said, but asymmetric crypto is rarely interchangeable with symmetric crypto - you usually use them in very different cases.
It still doesn't include everything you need of course: at the very least you'll still need to implement expiry validation and some token revocation mechanism. But you actually get more value out of the box than you get with a JWT library: your token content is encrypted, your using a solid crypto library and you don't have to mess around with any knob to secure your implementation.
It doesn't offer support for hot-swapping crypto algorithms, but for what it's worth, cryptographic agility is the root cause of many issues we had with TLS in the past[1].
If you end up needing both symmetric and asymmetric cryptography in your tokens, you better treat them as two different types of tokens, because they usually are. I think tptacek already said, but asymmetric crypto is rarely interchangeable with symmetric crypto - you usually use them in very different cases.
[1] https://www.imperialviolet.org/2016/05/16/agility.html