{
"$type": "site.standard.document",
"canonicalUrl": "https://johnnyreilly.com/posts/aspnet-core-allowlist-proxying-http-requests",
"description": "ASP.NET Core can proxy HTTP requests selectively, allowing only acceptable traffic via a middleware for specified paths.",
"path": "/posts/aspnet-core-allowlist-proxying-http-requests",
"publishedAt": "2019-02-22T00:00:00.000Z",
"site": "at://did:plc:yy3apqjlms24kso7ahn7lbmb/site.standard.publication/3mova7c4nho2b",
"tags": [
"asp.net"
],
"textContent": "This post demonstrates a mechanism for proxying HTTP requests in ASP.NET Core. It doesn't proxy all requests; it only proxies requests that match entries on an \"allowlist\" - so we only proxy the traffic that we've actively decided is acceptable as determined by taking the form of an expected URL and HTTP verb (GET / POST etc).\n\n\n\nWhy do we need to proxy?\n\nOnce upon a time there lived a young team who were building a product. They were ready to go live with their beta and so they set off on a journey to a mystical land they had heard tales of. This magical kingdom was called \"Production\". However, Production was a land with walls and but one gate. That gate was jealously guarded by a defender named \"InfoSec\". InfoSec was there to make sure that only the the right people, noble of thought and pure of deed were allowed into the promised land. InfoSec would ask questions like \"are you serving over HTTPS\" and \"what are you doing about cross site scripting\"?\n\nThe team felt they had good answers to InfoSec's questions. However, just as they were about to step through the gate, InfoSec held up their hand and said \"your application wants to access a database... database access needs to take place on our own internal network. Not over the publicly accessible internet.\"\n\nThe team, with one foot in the air, paused. They swallowed and said \"can you give us five minutes?\"\n\nThe Proxy Regroup\n\nAnd so it came to pass that the teams product (which took the form of ASP.Net Core web application) had to be changed. Where once there had been a single application, there would now be two; one that lived on the internet (the _web_ app) and one that lived on the companies private network (the _API_ app). The API app would do all the database access. In fact the product team opted to move all significant operations into the API as well. This left the web app with two purposes:\n\n1. the straightforward serving of HTML, CSS, JS and images\n2. the proxying of API calls through to the API app\n\nProxy Part 1\n\nIn the early days of this proxying the team reached for AspNetCore.Proxy. It's a great open source project that allows you to proxy HTTP requests. It gives you complete control over the construction of proxy requests, so that you can have a request come into your API and end up proxying it to a URL with a completely different path on the proxy server.\n\nProxy Part 2\n\nThe approach offered by AspNetCore.Proxy is fantastically powerful in terms of control. However, we didn't actually need that level of configurability. In fact, it resulted in us writing a great deal of boilerplate code. You see in our case we'd opted to proxy path for path, changing only the server name on each proxied request. So if a GET request came in going to https://web.app.com/api/version then we would want to proxy it to a GET request to https://api.app.com/api/version. You see? All we did was swap https://web.app.com for https://api.app.com. Nothing more. We did that as a rule. We knew we _always_ wanted to do just this.\n\nSo we ended up spinning up our own solution which allowed just the specification of paths we wanted to proxy with their corresponding HTTP verbs. Let's talk through it. Usage of our approach ended up as a middleware within our web app's Startup.cs:\n\nIf you look at the code above you can see that we are proxing requests to a single server: ServerToProxyToBaseUrl. We're also only proxying requests which match an entry on our allowlist (as represented by allowListProxyRoutes). So in this case we're proxying two different requests:\n\n1. GET requests to api/version are proxied through as _anonymous_GET requests.\n2. GET and POST requests to api/account/{accountId:int}/all-the-secret-info are proxied through as GET and POST requests. These requests require that a user be authenticated first.\n\nThe AllowListProxy proxy class we've been using looks like this:\n\nThe middleware for proxying (our UseProxyAllowList) looks like this:\n\nThis works out to be a flexible and simple approach to allowlist proxying.",
"title": "ASP.NET Core: Proxying HTTP Requests with an AllowList"
}