You could use hashed token claims to encode a role or other identifying info.
If you use a webhook you don't have to worry about this, since the user doesn't get to see what you forward as the authorization claims payload. But if you're using a JWT to hold state, since JWT's are publicly viewable it has to be non-obvious and non-deterministic.
Maybe I'm not understanding. But in a JWT you can verify the signature from when you originally signed that user's JWT. So even if I change some data in my JWT, on the backend it will not match the signature. So just reject if it doesn't match, say someone change permission:read to permission:admin
Roles are an approach towards query optimization, but the question placed a constraint on the user not knowing about them. Encoding the role information so it is not visible to the user is a potential solution.
By not knowing about them I mean that the customer/company never defines the roles as part of onboarding, etc. In this case they define the restrictions per-user.
If you use a webhook you don't have to worry about this, since the user doesn't get to see what you forward as the authorization claims payload. But if you're using a JWT to hold state, since JWT's are publicly viewable it has to be non-obvious and non-deterministic.
IE, something like:
Where in the example above, the key is "X-User-Role" and the value is "moderator" that have been bcrypt hashed.Probably you would want to choose more obscure/uncommon words. I'm not familiar with the details of hashing but it seems like a good idea.
Then you can attach the role (or other identifying) metadata to each request in a way that isn't immediately obvious what it's for.
Disclaimer: I'm not a security expert, possible (probable?) there is a gaping hole in this and it's an awful idea.