Should your auth-service be validating tokens?
Earlier this month, I happened to be in a technical discussion with a small team wherein I was being briefed about how authorization works in their microservice-based system.
Here's what a request to one of their protected resources looks like:
Though I've only depicted one Protected Service
, many of those exist. Also notice that there is no API gateway.
The responsibility of the Auth Service
in the figure is to:
- Generate Tokens on successful logins
- Validate Tokens
- Handle other Auth flows
This post is about point 2: should the Auth Service validate the tokens or should it be done by the individual services themselves?
Letting the auth-service validate tokens
Let's look at the pros and cons for this approach. Pros first:
- Easy to implement. Easy to maintain.
- The auth-service is responsible for all things auth. Since the implementations of token-generation and token-validation are generally related, it makes sense to keep them in one place.
Making the auth service perform token validation comes with it's own drawbacks that need to be considered too:
- Every microservice that needs authorization has a dependency on the Auth Service.
- The amount of load on the auth-service by every other protected service + the interservice communication overhead for a lightweight task of token validation is not always justified.
- The Auth Service is a single point of failure. If it fails, the dependent services are going to fail too.
Validating at every protected service
In this approach, the services that require authorization should validate the tokens. The protected service doesn't have to talk to the Auth Service
at all.
Now this does gives us some advantages over validating at the auth-service:
Protected Service
s are better isolated from theAuth Service
.- The single point of failure has been eliminated.
Now this might not always be possible, so let's look at some of the possible scenarios:
- Opaque Guid-style tokens
- Generated tokens are stored in a datastore.
- This datastore is queried each time to check if the token is valid.
- Tokens are revoked by removing it from the datastore.
- Bearer tokens
- The principal claims are part of the token.
- No datastore query required to validate the tokens.
- Cannot be revoked. Has a short TTL.
- Revocable bearer tokens
- Same as bearer tokens except that a custom revocation logic now exists which requires querying the datastore.
Considerations while validating tokens at each service
- Guid-style tokens and revocable bearer tokens will still require some minimal amount of communication between the
Protected Service
and theAuth Service
. - Reducing inter-service communication always results in some kind of redundancy. In this case it's going to be redundant implementations of the token validation logic in every
Protected Service
. - The
Protected Service
s' implementations (as opposed to the services themselves) will be a tad more coupled with theAuth Service
. New iterations of theAuth Service
need to be backward compatible with old implementations of the validation logic. This is especially true when using revocable bearer tokens.
Communication with the auth-service will be required whenever a datastore access is involved (since the AuthService owns its datastore). This can be reduced however by caching the required data for example.
The hybrid approach
A hybrid approach can be used to address the last two considerations listed previously. The idea is to let the service handle token validation and use the auth service as a fallback mechanism for token validation.
The Protected Service
's validation implementation could be put behind a configuration toggle and disabled to forcefully use the fallback mechanism if required.
Conclusion
While it's okay to use the Auth Service
for validation when starting out, I think it makes more sense to prefer validation at the individual protected services whenever possible.
At the end of the day, like many things in engineering, this too is about tradeoffs.