Background
Lets start from the top here. Your server receives a request:
Hi server,
Can you do X for me?
- Jane
Access Control is when the server checks to see that Jane is actually allowed to do X before processing the request. Generally speaking, all apps will have some endpoints that need an access control check -- maybe Jane is not allowed to delete records from the DB, or maybe Jane is not allowed to change settings of Bob's account, etc.
OWASP's point is that in an old-school app, when the user logs in, you might build a session info object telling you who the user is and what permissions they have, and keep that in server memory until the user logs out. Obviously that does not scale with modern cloud architectures.
JWT To The Rescue!
The key part of that OWASP quote is (emphasis mine)
user authentication should be centralised in a Identity Provider (IdP), which issues access tokens
Take anything that your microservices need to know about the user (ie anything that you would traditionally have put in the sessionInfo object) and stuff it in a JWT, hand it to the user, and have them hand it back with each REST call. Omitting the crypto parts of a JWT, the claims you care about might be:
{
"sub": "48",
"name": "Jane",
"admin": true,
"exp": 1621004706
}
Now, by unpacking the JWT from the request, microservices have everything they need to make an access control decision; no network calls needed! Trying to delete data? Do they have Admin: true
? Trying to change Bob's settings? Are they Bob?
To make this all secure, we rely on the fact that JWTs are cryptographically signed (and sometimes encrypted, but that is optional) by the IdP. So to make this whole thing work, the only piece of info the microservice needs is the cryptographic key to verify the signature on the JWT. This is generally static, so you can stick it in config data or whatever. If the IdP is external (say you've integrated with Google or GithHub's SSO service) then the JWT verification key will be an RSA public key. If your IdP is an internal component of your app, then you can simplify a bit and use a symmetric HMAC key where the IdP and the microservices all have the same "JWT secret" in their config.
Service-to-service
Question from comments: is this model applicable for a service-to-service API?
Sure, why not?
Your client, even an automated one, will typically have some sort of long-term credential, typically an API key or a TLS client cert. You still want to make the client authenticate against a central IdP for a number of reasons:
- The long-term credential usually does not carry all the meta-data. So something in your backend needs to look up the associated meta-data.
- What if that access changes? Say you're auditing your config and realize you gave that component wider DB access than it really needs, or heaven-forbid someone accidentally posts that client secret key in a public git repo and you need to revoke it.
Doing the full access control check against the DB on every HTTP request is really expensive, but issuing a JWT with an expiry timestamp and forcing the client to re-do the /auth/login
every 15 mins / 1 hr / 24 hrs is usually a reasonable security-performance tradeoff.
In some simple cases you may not have any meta-data and you just want to check that a request is coming from another authorized component; basically a "Are you me?" check. In these cases I've seen each component get a copy of the same symmetric JWT and just create themself a token before each request.