Creating a proxy to another web api with Asp.net core
Solution 1
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
Solution 2
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
Solution 3
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request
to a reusable HttpRequestMessage
. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage
to the HttpContext.Response
so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return Ok();
}
And try to access it. It will be proxied to google.com
Solution 4
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
Solution 5
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Comments
-
Gimly almost 2 years
I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
- Client requests a token by making a POST to a unique URL that I sent him earlier
- My app sends him a unique token in response to this POST
- Client makes a GET request to a specific URL of my app, say
/extapi
and adds the auth-token in the HTTP header - My app gets the request, checks that the auth-token is present and valid
- My app does the same request to the external web API and authenticates the request using BASIC authentication
- My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet] public async Task GetStatement() { //TODO check for token presence and reject if issue var queryString = Request.QueryString; var response = await _httpClient.GetAsync(queryString.Value); var content = await response.Content.ReadAsStringAsync(); Response.StatusCode = (int)response.StatusCode; Response.ContentType = response.Content.Headers.ContentType.ToString(); Response.ContentLength = response.Content.Headers.ContentLength; await Response.WriteAsync(content); } [HttpPost] public async Task PostStatement() { using (var streamContent = new StreamContent(Request.Body)) { //TODO check for token presence and reject if issue var response = await _httpClient.PostAsync(string.Empty, streamContent); var content = await response.Content.ReadAsStringAsync(); Response.StatusCode = (int)response.StatusCode; Response.ContentType = response.Content.Headers.ContentType?.ToString(); Response.ContentLength = response.Content.Headers.ContentLength; await Response.WriteAsync(content); } }
_httpClient
being aHttpClient
class instantiated somewhere else and being a singleton and with aBaseAddress
ofhttp://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
-
Dmitriy over 6 yearscould you share your implements of your middleware? If it possible. Is it strongly based on .Net Core? Thanks.
-
Gimly over 6 years@Dmitriy No, I'm sorry I cannot share the implementation as it is part of a closed source program. But it's basically the same code as in the question implemented as a middleware. Check the github.com/aspnet/Proxy/blob/dev/src/Microsoft.AspNetCore.Proxy/… file to get an idea on how to start the middleware.
-
Kugel over 6 yearsCan you update the nuget package this code does not work with published 0.2.0
-
Allan over 6 yearsNot sure if I am missing something with this code or not but I cannot resolve
services.AddProxy(...)
. I am using Microsoft.AspNetCore.Proxy v0.2.0. Also, theRunProxy
method does not accept aUri
as a parameter. What version was used for this example? -
Kerem Demirer over 6 yearsI used v0.2 nuget from preview feeds: dotnet.myget.org/feed/aspnetcore-release/package/nuget/…
-
Bangyou about 6 yearsI have the same problem with @Allan. Is any solution for it?
-
Bangyou about 6 yearsIt seems the SDK not support asp net core 2.1 when I compile the source codes.
-
amin89 about 6 yearsSame thing.cannot resolve services.AddProxy() function with Microsoft.AspNetCore.Proxy v0.2.0 ... Since March 2018, can anyobody give us the solution??
-
JPelletier almost 6 years@amin89 The method name was changed to RunProxy. It's not clear if the feature is experimental or not github.com/aspnet/Home/issues/2931
-
amin89 almost 6 years@Allan I found it if u still need the solution.
-
Jsandesu over 5 yearsThanks. Couldn't get the ProxyRoute attribute to work. Got a 404. Probably something I was doing wrong. Had success using the UseProxy() method, so thanks again.
-
twitchax over 5 yearsAhhh, nice. Feel free to file a bug on that. Should be an easy fix.
-
twitchax over 5 yearsCheck out the solution below. The middleware does not take the class route into account yet. Feel free to file an issue! :)
-
BrunoMartinsPro over 4 yearsWas this the only configuration you made?
-
BrunoMartinsPro over 4 yearsnever mind i changed the "api/someexternalapp-proxy/{arg1}" to "api/someexternalapp-proxy/{**catchall}" and added in ConfigureServices "services.AddProxies();" and its now working!
-
Eddie over 4 yearsThis proxy also doesn't add the query string to requests that it proxies. I added that in
BuildTargetUri
usingstring query = request.QueryString.ToString();
-
genuinefafa over 3 yearsoh @spencer741, this is pretty interesting and seems too easy too set up
-
spencer741 over 3 years@genuinefafa Yes indeed... is definitely in early stages. Wouldn't trust from a security or performance perspective just yet.
-
spencer741 about 3 yearsLooks like it is very promising now.
-
Renato Sanhueza about 3 yearsThe problem with this is that images and scripts with relative paths will not load correctly.
-
Michael about 2 yearsThanks for the code. There's enough here to guide down the path I want to go.