Saturday 24 September 2022

JWT Autentication

 You can use JWT to carry the identity of the calling microservice, or the identity of the end user or system that initiated the request. JWT can also be used to propagate identity attributes between multiple trust domains.


              Use cases for securing microservices with JWT

JWT addresses two main concerns in a microservices security design: securing service-to-service communications and passing end-user context across microservices (figure 7.1). As we discussed in chapter 6, JWT isn’t the most popular option for securing service-to-service communications; mTLS is. In this section, we discuss why you might pick JWT over mTLS to secure service-to-service communications, as well as other use cases of JWT in a microservices deployment. In practice, you use JWT along with mTLS, together, in most cases.




Figure 7.1 Propagating the end user’s identity in a JWT among microservices. All the microservices in the deployment trust the STS. The API gateway exchanges the JWTs it gets from client applications for new JWTs from this STS.



               Sharing user context between microservices with a shared JWT

When the identity of the microservice isn’t relevant, but the identity of the end user (a system or a human) is, you should consider using JWT instead of mTLS. In this case, services themselves don’t authenticate to one another. In every request, you need to carry the identity of the end user who initiates the message flow; if not, the recipient microservice rejects the request. But in practice, even though you do not worry about the service’s identity, in terms of better security you should still use mTLS among microservices, along with the JWT protection. This will add a second layer of defense. The following walks you through the numbered request flow shown in figure 7.1:

  1. An end user initiates the request flow. This end user can be a human or a system.

  2. As discussed in chapters 1 and 3, the edge gateway authenticates the end user. The edge gateway intercepts the request from the end user, extracts the token (which can be an OAuth 2.0 reference or self-contained token), and then talks to the STS connected to it to validate the token. Then again, the token that the end user presents might not be issued by this STS; it can come from any other identity provider that this STS trusts. The details related to the end user authentication are discussed in chapter 3. The STS should know how to validate the token presented to it in this step.

  3. After validating the token, the STS issues a new JWT signed by itself. This JWT includes the user details copied from the old JWT (from step 2). When the edge gateway passes the new JWT to the upstream microservices, those upstream microservices need only trust this STS to accept the token as valid. Typically, all the microservices within a single trust domain trust a single STS.

  4. The API gateway passes the new JWT issued by the STS in an HTTP header (Authorization Bearer) over TLS to the Order Processing microservice. The Order Processing microservice validates the signature of the JWT to make sure that it’s issued by the STS it trusts. Apart from the signature validation, the Order Processing microservice also does audience validation, checking whether the value of the provided JWT’s aud is known to itself (more details in appendix B). For the pattern discussed in this section to work, all the microservices in the same trust domain (that trust a single STS) must accept a JWT with a wildcard audience value such as *.ecomm.com.

  5. When the Order Processing microservice talks to the Inventory microservice, it passes the same JWT that it got from the API gateway. The Inventory microservice validates the signature of the JWT to make sure that it’s issued by the STS it trusts. Also, it checks whether the value of the aud attribute in the JWT is *.ecomm.com.

In this approach, JWT helps you achieve two things. First, it helps you pass the end-user context across microservices in a manner that can’t be forged. Because the claims set of the JWT is signed by the STS, no microservice can change its content without invalidating its signature. Also, JWT helps you secure service-to-service communications. One microservice can access another microservice only if it carries a valid JWT issued by the trusted STS. Any recipient microservice rejects any request without a valid JWT.



S            Sharing user context with a new JWT for each service-to-service interaction

The use case we discuss in this section is a slight variation of the one we discussed in section 7.1.1, but still only the end user’s identity is relevant--not the identity of the microservice. Instead of passing the same JWT across all the microservices and accepting the same audience value at each microservice, you generate a new JWT for each service interaction. This approach is much more secure than using a shared JWT. But there’s no such thing as absolute security. Everything depends on your use cases and the level of trust you have in your microservices deployment.

Figure 7.2 illustrates how this pattern works. It’s the same flow discussed in section 7.1.1 except for steps 4a and 4b. In step 4a, before the Order Processing microservice talks to the Inventory microservice, it talks to the STS and does a token exchange. It passes the JWT it got from the API gateway (issued under op.ecomm.com audience) and requests a new JWT to access the Inventory microservice. In step 4b, STS issues a new JWT under the audience iv.ecomm.com. Thereafter, the flow continues as in the preceding section.



Figure 7.2 Propagating the end user’s identity in a JWT among microservices with token exchange


Why do you need a new JWT with a new audience value when the Order Processing microservice talks to the Inventory microservice? Why is it more secure than sharing the same JWT coming from the API gateway across all the microservices in the deployment and accepting a single audience value? There are two valid reasons at minimum:

  • When you have a one-to-one mapping between a microservice in your deployment and the audience value of the corresponding JWT issued by the STS, for a given JWT, you know exactly who the intended audience is. In step 4 of figure 7.2, for example, when the request is dispatched to the Order Processing microservice from the API gateway, it can make sure that the token goes to no other microservice but Order Processing. If the token goes to the wrong microservice, it will still be rejected by that microservice because of the audience mismatch.

  • If the Order Processing microservice tries to reuse the token given to it as-is to access another service, such as the Finance microservice (which ideally, it shouldn’t need access to), the request fails because the audience value in the original JWT doesn’t work with the Finance microservice, which has its own audience value. The only way that the Order Processing microservice can talk to the Finance microservice is to pass its current JWT to the STS and exchange it for a new JWT with an audience value accepted by the Finance microservice. Now you have more control at the STS, and the STS can decide whether to let the Order Processing microservice access the Finance microservice.

             Sharing user context between microservices in different trust domains


Figure 7.3 Cross-domain authentication and user context sharing among multiple trust domains. The STS in the delivery domain trusts the STS in the ecomm domain.


                Self-issued JWTs

In the use cases discussed so far, we didn’t need to worry about the identity of the microservice itself. Rather, we relied on a JWT issued by a trusted STS that carried the end user’s identity. With self-issued JWTs (see figure 7.4), however, we do need to be concerned about the identity of microservices when they talk to one another, as in mTLS (discussed in chapter 6).


Figure 7.4 Self-issued JWT. The JWT is signed using the private key of the Order Processing microservice.

As in mTLS, and in this model, each microservice must have its own public/private key pair. Each microservice generates a JWT, signs it with its own private key, and passes it as an HTTP header (Authorization Bearer) along with the request to the recipient microservice over TLS. Because the JWT in this case is a bearer token, the use of TLS is highly recommended (or in other words, a must). The recipient microservice can identify the calling microservice after verifying the JWT signature by using the corresponding public key.

How does this process differ from mTLS? If what you’re trying to achieve is only authentication between two microservices, neither method is superior. From the developer overhead point of view, setting up mTLS is more straightforward than using self-issued JWTs. Both techniques need to handle all the key management challenges discussed in chapter 6, along with service-to-service authentication. If you intend to share contextual data (not just the business data) between two microservices, the self-issued JWT is much better than mTLS. If the Order Processing microservice wants to share the order ID with the Inventory microservice as a correlation handle, for example, it can embed it in the JWT. In case of mTLS, you need to pass it as an HTTP header.

What’s the difference? mTLS provides confidentiality and integrity of the data in transit, but not nonrepudiation. Nonrepudiation cryptographically binds an action to the person who initiated it so that they can’t deny it later. With mTLS alone, you can’t achieve nonrepudiation. But when you use a self-issued JWT, all the data added to it is bound to the owner of the corresponding private key that’s used to sign the message, and helps you achieve nonrepudiation. Even if you use a self-issued JWT, in most cases the communication between the two microservices must happen over TLS (not mTLS), which protects the confidentiality and integrity of the communication. If you want to get rid of TLS, you can use a signed, encrypted JWT and still achieve those attributes. But you’ll rarely want to get rid of TLS.


NOTE A JWT is a bearer token. A bearer token is like cash. If someone steals $10 from you, they can use it at any Starbucks to buy a cup of coffee, and no one will ask for proof that they own the $10. Anyone who steals a bearer token can use it with no issue until the token expires. If you use JWT for authentication between microservices (or in other words, authenticate one microservice to another), you must secure the communication channel with TLS to minimize the risk of an intruder stealing the token. Also, make sure the JWT is short-lived. In that case, even if someone steals the token, the impact of the stolen token is minimal.


            Summary

  • A JWT, which provides a way to carry a set of claims or attributes from one party to another in a cryptographically secure way, plays a key role in securing service-to-service communications in a microservices deployment.

  • You can use a JWT to carry the identity of the calling microservice, or the identity of the end user or system that initiated the request.

  • JWT addresses two main concerns in a microservices security design: secur- ing service-to-service communications and passing end-user context across microservices.

  • When the identity of the microservice isn’t relevant, but the identity of the end user (system or human) is, you should prefer using JWT over mTLS. But still, in practice you will use JWT with mTLS together to build a second layer of defense.

  • Having a different or new JWT for each interaction among microservices is a more secure approach than sharing the same JWT among all the microservices.

  • JWT can be used for cross-domain authentication and attribute sharing.

  • A self-issued JWT is issued by a microservice itself and used for authentication among microservices.

  • A nested JWT is a JWT that embeds another JWT. It carries the identity of both the calling microservice and the end user.

No comments:

Post a Comment