0

When developing microservices which call each other using OAuth2/client credentials auth, where services have their own credentials, I regularly find myself writing code to:

  1. Exchange a static client_id and client_secret for a time-limited JWT Bearer token.
  2. Use that token many times, for many requests, until it expires (or is close to expiring).
  3. Get a fresh token, and repeat.

Given that caching is one of the two hard problems in computer science (along with naming and off-by-one errors), I'd love to be able to find a library that already does this, or failing that, write one.

A wish list of features:

  • Underlying non-blocking http client.
  • The user can configure details about authentication once, and then not have to think about it every time they make an api request.
  • The user can use it to call any arbitrary api.
  • After writing code to define the api endpoints/models/etc, there is some simple method like .invokeWithAuth or something that can handle requesting a token (if necessary) or adding a valid token (if cached) to the request.
  • Except perhaps when request volume is very low, most requests should use a cached token, rather than requesting a new one with each request.
  • The library should have an effective means of managing token expiration, like handling a 401 response with a retry using a new token, or preemptively refreshing tokens before they expire.
  • The library should be able to determine (perhaps by a user-implemented interface) how long a particular token is valid for, from the token response.

I would be happy to find a library that does even some of that, but I've had no success. Does anyone know of or use a library that does a good job solving this problem? Are there other solutions I overlooked? I could write code to do all of this, but I would probably miss some edge cases.

I'm actually writing code in scala, so a scala library would be even better than a java one, but java libraries are perfectly usable from scala.

mbbush
  • 182
  • 12
  • Possibly related to https://stackoverflow.com/questions/56527414/is-there-a-way-to-use-java-net-http-httpclient-with-oauth2 – swpalmer Feb 10 '22 at 23:52
  • @swpalmer No, not especially related. The answer to that question is a naive implementation that requests a new token for each call, which is exactly what I'm trying to avoid. – mbbush Feb 11 '22 at 02:17

1 Answers1

1

It's an important concern, though there is not really a one size fits all solution. I would definitely aim to produce a utility client library that you can share between microservices, to do common security and reliability handling.

COMMON REQUIREMENTS

  • Different OAuth flows
  • 401 retries
  • Synchronized token refresh
  • Token caching
  • Headers for logging and correlation
  • Consistent timeouts
  • Consistent errors and logging
  • Handling unavailability, eg circuit breaker

POSSIBLE APPROACH

I like to define two types of class that act as service agents, where the former uses the latter. This reduces code in other parts of the app to one liners. Here is some Kotlin code from an Android app of mine:

In a company setup I would also factor out shared fetch logic in a shareable way.

API SIMPLIFICATION

Out of interest, microservices from the same conpany can usually forward JWTs to each other, while still ensuring a Zero Trust architecture. The article on Scope Best Practices explains this.

Benefits:

  • Simpler code, since microservices only need to act as resource owner and not as an OAuth client
  • Maintains the original caller in a secure way, and avoids possible insecure practices such as passing user IDs in custom headers
  • Less load on the Authorization Server and fewer clients to manage

As an example, the above Android client calls this Java API which only validates JWTs and does not need to get or cache tokens.

Within an organizational boundary it can sometimes work best if only UIs are OAuth clients. Scope checks prevent obviously invalid calls between APIs.

If required, infrastructure security such as Kubernetes network policies can further lock down machine to machine connections, and this can be easier to manage than lots of client IDs and secrets.

SUMMARY

These are just some thoughts based on my own experience.

halfer
  • 19,824
  • 17
  • 99
  • 186
Gary Archer
  • 22,534
  • 2
  • 12
  • 24