Forwarding authenticated calls to a downstream API using YARP

profile
Tim Deschryver
timdeschryver.dev

Series: Building a secure application (3/3)

  1. Using YARP as BFF within .NET Aspire: Integrating YARP into .NET Aspire
  2. Secure your Yarp BFF with cookie-based authentication
  3. Forwarding authenticated calls to a downstream API using YARP

So far, we've set up a Backend for Frontend (BFF) that handles the user authentication flow and serves as an intermediary between the frontend and backend services. This BFF is the only application that's exposed to the public internet, while the other services are protected behind it.

While the BFF handles the first layer of security, it's crucial to ensure that the backend services also validate the user's identity and permissions. In this post, we'll explore how to forward authenticated calls to a downstream API using YARP (Yet Another Reverse Proxy) (the red arrow in the picture below). This is particularly useful in scenarios where you have a BFF architecture and need to securely communicate with other services on behalf of the user.

The overall architecture of the application
Note

As we've discussed in previous posts, the BFF pattern helps to centralize the handling of authentication and authorization, making it easier to manage security concerns. By using YARP, we can efficiently route requests from our BFF to downstream APIs while ensuring that the user's identity and permissions are correctly propagated.

How does service-to-service authentication work link

The BFF stores a cookie with user's session information, and this information needs to be passed down to the next API in the chain. However, we're not going to forward the cookie directly. Instead of forwarding the cookie, we will obtain the access token from the user's session using OAuth 2.0 Token Exchange, and include the token in the Authorization header of the request towards the downstream API. This practice makes the setup scalable, and we're following the best practices for building API backends.

Why is an access token preferred link

But why do we add this overhead instead of forwarding the cookie directly? There are several reasons:

Implementing the solution link

Setup using Duende.AccessTokenManagement.OpenIdConnect link

While it's possible to retrieve the access token manually (through context.HttpContext.GetTokenAsync("access_token");), it doesn't automatically handle token expiration and most importantly the refresh cycle when the token is expired. To simplify the implementation, we can use the Duende.AccessTokenManagement.OpenIdConnect library, which provides built-in support for token management, including automatic token refresh. This functionality was also planned to be included in ASP.NET v10, but sadly it's postponed for now.

To install the Duende library, run the following command in your BFF project:

Next, register the access token management services using AddOpenIdConnectAccessTokenManagement() in your Program.cs file. Because it makes use of a cache to store the tokens, you might also need to add a cache implemention, the example below uses the distributed memory cache.

Create a YARP transformer link

YARP provides a flexible way to modify requests and responses using transformers.

To modify the outgoing requests and add the access token to the Authorization header, we need to create a custom transformer by implementing the RequestTransform base class.

The transformer below checks if the user is authenticated, retrieves the access token using the extension method GetUserAccessTokenAsync() (coming from the Duende library), and adds it to the Authorization header of the outgoing request.

The method GetUserAccessTokenAsync() takes care of retrieving the access token from the user's session and handles token refreshes if necessary.

Tip

If you're not using YARP, you can still use the same logic to add the access token to the outgoing HTTP requests of a HttpClient. For more information, check the official documentation.

Register the transformer with YARP link

Finally, we need to register the custom transformer with YARP in the AddReverseProxy method. This can be done by adding a call to AddTransforms and including our transformer in the configuration. Because we only want to add the access token when the route requires authentication, we can check if the AuthorizationPolicy is set on the route. This is a step not to be missed, as we don't want to add the Authorization header when it's not needed, for example when calling external services that are not in our control.

After all previous steps, the outgoing request will contain both the Authorization header with the access token. This means we can remove the cookie from the outgoing request, which is done by adding a RequestHeaderRemoveTransform to the transforms collection.

Conclusion link

In this post, we've explored how to forward authenticated calls to a downstream API using YARP in a BFF architecture. By leveraging the Duende.AccessTokenManagement.OpenIdConnect library, we simplified the process of managing access tokens and ensured that our backend services can securely authorize requests on behalf of the user. By simply configuring YARP to use a custom transformer, we can keep this logic encapsulated and reusable across different routes and services. This approach enhances the security of our application architecture.

Feel free to update this blog post on GitHub, thanks in advance!

Join My Newsletter (WIP)

Join my weekly newsletter to receive my latest blog posts and bits, directly in your inbox.

Support me

I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.

Buy Me a Coffee at ko-fi.com PayPal logo

Share this post