I typically use a service like AWS cognito (using their built-in hosted UI) to handle authentication for my apps. That gives me MFA, Google/Facebook login, email verification, etc for free and has a generous free tier.
I have a template that's backed by terraform and the authentication client is in lambda so the whole thing is serverless, self-contained and practically free. So I just run "terraform apply" and I have scalable auth for my new service.
If any service I create is lucky enough to break out of the free-tier and cost is an issue, then I can just move to another OAuth2/OIDC provider. The auth mechanism Cognito uses is just a specification meaning I am not coupled to any one service provider (though the user accounts themselves are). Cognito, Auth0, IdentifyServer, or whatever - I can migrate if cost becomes a problem.
The big issue with JWTs are that, if lost, they give permissions to attackers without revocability.
For this reason, I keep auth-tokens short lived and refresh them often. Refresh-tokens are revocable and live for a few days. This means that a lost auth-token is only harmful for a few minutes while a lost refresh token is only harmful until revoked or expired.
Tokens are stored as path-specific http-only cookies so the only vector for attack is if a user physically opens devtools and gives an attacker the token - or if the attacker has access to the computer (physically or via a malicious terminal script).
High risk operations (e.g. delete account, delete content, anything high risk) requires "step-up" authentication - so a user is asked to re-authenticate in those cases.
Overall, when you consider that rolling your own authentication comes with the liability associated with holding user data (companies must announce a breach to users, etc) - if a service provider like Cognito is compromised, you won't be liable or the only one affected.
JWTs have security concerns, but on balance, when used with third party provider, a sensible configuration and considering the risk of rolling your own - they are fine.
I have a template that's backed by terraform and the authentication client is in lambda so the whole thing is serverless, self-contained and practically free. So I just run "terraform apply" and I have scalable auth for my new service.
https://github.com/alshdavid/template-cognito (only 1 dependency on AWS, everything else is stdlib)
If any service I create is lucky enough to break out of the free-tier and cost is an issue, then I can just move to another OAuth2/OIDC provider. The auth mechanism Cognito uses is just a specification meaning I am not coupled to any one service provider (though the user accounts themselves are). Cognito, Auth0, IdentifyServer, or whatever - I can migrate if cost becomes a problem.
The big issue with JWTs are that, if lost, they give permissions to attackers without revocability.
For this reason, I keep auth-tokens short lived and refresh them often. Refresh-tokens are revocable and live for a few days. This means that a lost auth-token is only harmful for a few minutes while a lost refresh token is only harmful until revoked or expired.
Tokens are stored as path-specific http-only cookies so the only vector for attack is if a user physically opens devtools and gives an attacker the token - or if the attacker has access to the computer (physically or via a malicious terminal script).
High risk operations (e.g. delete account, delete content, anything high risk) requires "step-up" authentication - so a user is asked to re-authenticate in those cases.
Overall, when you consider that rolling your own authentication comes with the liability associated with holding user data (companies must announce a breach to users, etc) - if a service provider like Cognito is compromised, you won't be liable or the only one affected.
JWTs have security concerns, but on balance, when used with third party provider, a sensible configuration and considering the risk of rolling your own - they are fine.