1) One system produces a JWT which makes a bunch of "claims" about the bearer; e.g. 'their email address is alice@example.org'.
2) Another system which trusts the first system can consume these tokens and use the "claims" to authenticate the bearer, e.g. 'the email address alice@example.org is associated with user ID 12345, so the bearer must be user 12345'.
3) Now that the user is authenticated, the second system can initiate a session for them (e.g. storing a cookie in their browser, or whatever).
4) From now on that JWT should be rejected; we can approximate this by making it short-lived, but we can do better by the second system update some state. For example, updating the "last seen" time of a user, and rejecting any tokens issued before the this time.
The idea of 'using stateless JWTs for authentication' is presumably the common practice of avoiding steps 3 and 4: rather than having the second system initiate a session for the user, the JWT is treated as if it were a session token; rather than invalidating the JWT after its first use, it is accepted over and over again as part of the user's subsequent requests. This also requires the JWTs to have a longer expiry time than necessary, since they have to survive until the user's last interaction, rather than their first (in fact 'refresh tokens' can be used as well, but the principle is the same).
As for why it's a bad idea, I recently came across PASETO (a simplified alternative to JWT) which specifically calls this out as an insecure practice due to the possibility of replay attacks ( e.g. https://github.com/paragonie/paseto/tree/master/docs#was-sta... )
Do we use certificates for authentication? Yes, and it works well.
Do we use certificates for authorization? Not likely. Instead, usually we ensure ID of entity which we communicate with (as result of authentication) and lookup it's permissions in database.
Do we recognize any certificate body as immediate permission requisite? No! Instead, we have signed claim with public key which entity should present in order to prove it's identity (and prove possession of private key later). I. e. we issue end-entity certificate which describes and verifies public key of that entity.
Do we NEED to revoke these end-entity certificates or have them expiring really fast for normal operations like logout? No. There may be something similar to CRL and OCSP for handling emergencies, but this is optional. Instead, we are not trying to handle authorization tasks with authentication mechanism. It's different things. We may just change status for authenticated entity in authorization database. Like: "user:admin;device:XXXXX;status:terminated".
What semantical meaning has revocation/expiration of token issued to alice@example.org? She is no longer alice? I doubt that. She is no longer allowed here? Or her specific device is no longer allowed here? Or her service subscription is just expired? Either way it's not a question for part which recognizes known entities and must work in a "yes" or "no" fashion.
It's not wrong to use JWT for authentication. It's wrong to use ANY authentication for ANY authorization.
I feel like the problem is generally ignored to some extent. I was writing my own (stateful, one database lookup per request) SSO system and ran into other problems. For example, how do you terminate all TCP connections that the user has open when revoking the token? No proxy I could find had that level of granularity exposed through its control plane API. They are all living in an HTTP/1.1 world where requests were short-lived, and it was acceptable to handle revocation at the next request. I kind of doubt it's ever caused any problems (employees on their last day don't seem to open long-lived gRPC connections to internal infrastructure for whatever reason), but it's one of those things that's going to burn someone someday.
(I seriously considered modifying envoy to keep track of (user, downstream TCP connection) so that they could be forcibly closed via an API when necessary, but decided a global rolling restart of the frontend proxy was acceptable in the extremely rare instance when some sort incident required terminating all of a user's TCP connections. Engineering time vs. security tradeoff. But then there are companies with 100s of developers that will ship you custom software to manage authentication, and they don't have this feature. That seems shady to me. Or maybe not so much shady, just exceedingly lazy, like they haven't really thought much about their product.)