My solution is to add 1 claim (sjti for signed_jti) and sign it with a user secret before signing the token with the app secret.
When a token comes in, I first validate it with the app secret. If not valid or expired, we return a 401.
If valid, I validate the sjti with the user secret since I now know who it belongs to.
The extra lookup for the user's secret then adds minimal overhead because we've already pre authenticated and can be considered business logic.
Thus, revoking all user sessions requires simply changing the user's secret.
What do you guys think about this? Any holes?