System Design — 2 way JWT, not 2 way TLS, JWS — JWE
Overview
The problem — MASSL/ 2 way TLS is complex most of the times and certificate/ PKI management is also complex. Creating certificates for clients and servers, then renewing it is quite complex and process oriented. Further, configuring the clients with truststores & keystores is again an added complexity.
Complexity with middleware(API gateways), introspection based security mechanisms like OAUTH2 is also complex(though they have their own pros).
Here I came up with a possible solution, an alternative solution for problems listed above. With this solution we have data integrity during request and data authenticity during response.
Have labeled this solution as 2 way JWT, where JWT is used for both request and response.
Request is JWS and Response is JWE.
POC Code
There are two repositories:-
left-side — https://github.com/neuw-medium/spring-left-side
right-side — https://github.com/neuw-medium/spring-right-side
Both are Spring Boot and Java based code repos.
left-side? left side as the name suggests, it is the client side also exposed as an API.
right-side? right side is the resource that left side fetches, available as API to left-side.
components/ libraries:-
- JDK11
- Java
- Maven
- Spring-Boot
- Spring-Webflux
- Nimbus-Jose
Flow Diagram
High level flow diagram is as below and notes for the steps are described after the diagram.
The left-side application is simply a client (inside the code you will find a WebClient) on top of the right-side application
And it exposes an endpoint like http://localhost:30011/apis/v1/left
Flow diagram notes step by step:-
- Left-Side generates a JWS payload that right-side is going to rely on which may have custom claim(s) for the data object(s).
- The payload is then signed using the private key(the key type is SIG) and we get JWS token. The private JWKS in this scenario is present on the left-side application and public JWKS is available over the endpoint — http://localhost:30011/.well-known/jwks
The request body is generated and JWS token is sent as part of request body to the right-side over the endpoint :-
POST http://localhost:30012/apis/v1/right - right-side application loads the JWKS in memory upon boot in advance. the input JWS token’s signature etc. is validated and here it only needs the public key.
- Validate the issuer/ audience. Extra check
- The request claims are extracted, and the business logic is based on input claims. In the example code of right-side repository, we are totally mocking the response so not majorly relying on the input claims for creating the response data.
- Create the response data object (relies on the input but for the setup here it is being mocked irrelevant to the input).
- Generate the JWE with header, payload, claims, add data object to the claim, etc. & then encrypt using one of the keys(public keys) from the left-side’s JWKS and ultimately get the JWE token as string.
The JWE is made part of the response object. The Key used is of type ENC. - left-side gets the response and decrypts the JWE based on the key(private key), which is already part of the left-side’s source code.
Observations & Conclusion
In the above article we explored the 2 way JWT solution as an integration option where both request and response data are shared via jwt.
In present article request is JWS (signed JWT) while response is JWE (encrypted JWT). Different flavors can also be explored like — 2 way JWE, 2 way JWS.
JWE as part of request and JWS part of response might be least possible solution because sharing private keys is never a great thing.
Private keys are also on the same side and stored as a private JWKS. Though the public keys for verifying the signature during request and encrypting during the response are part public JWKS exposed by the calling system(left-side)
Pros and Cons(detailed practical evaluation is still in progress)
You can reach me over LinkedIn for any questions and queries:-