-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
I am not sure if SpaProxy was designed to allow proxying to a sub path such as "http://localhost:8080/app" as the documentation does not really say anything about it's behaviour, just that the parameter is the 'target base URI'.
In testing i found some odd results. To check the proxied request i just started up the node http server pointing to the 'dist' dir like this: http-server dist --cors and observed the console output
First, without a sub path everything is as expected:
app.UseSpa(spa => {
spa.UseProxyToSpaDevelopmentServer(baseUri: "http://localhost:8080");
}
});
Request to https://localhost:5001 goes to http://localhost:8080/
Request to https://localhost:5001/ goes to http://localhost:8080/
Request to https://localhost:5001/test goes to http://localhost:8080/test
Request to https://localhost:5001/test/ goes to http://localhost:8080/test/
When a sub path is used it is clear that the /app subpath is ignored:
// The behaviour is the same for "http://localhost:8080/app" and "http://localhost:8080/app/"
app.UseSpa(spa => {
spa.UseProxyToSpaDevelopmentServer(baseUri: "http://localhost:8080/app");
}
});
Request to https://localhost:5001 goes to http://localhost:8080/
Request to https://localhost:5001/ goes to http://localhost:8080/
Request to https://localhost:5001/test goes to http://localhost:8080/test
Request to https://localhost:5001/test/ goes to http://localhost:8080/test/
Looking at the source code:
https://github.com/aspnet/JavaScriptServices/blob/9c6a1b577f04d845152532c66252811260b3f36a/src/Microsoft.AspNetCore.SpaServices.Extensions/Proxying/SpaProxy.cs#L61-L63
It is clear why this is the case, i don't think that method is the best choice for SpaProxy as it does not handle various scenarios very well. The Uri(baseUri, relativeUri) constructor method seems to treat the baseUri parameter as only "http(s)://host:port" ignoring any paths, well at least it does at first glance.
However, looking at the docs for Uri, the intended behaviour is rather odd:
https://docs.microsoft.com/en-us/dotnet/api/system.uri.-ctor?view=netcore-2.1
If the baseUri has relative parts (like /api), then the relative part must be terminated with a slash, (like /api/), if the relative part of baseUri is to be preserved in the constructed Uri.
Additionally, if the relativeUri begins with a slash, then it will replace any relative part of the baseUri
That behaviour does not suit the SpaProxy very well, i think it is reasonable to assume that the proxy should forward any paths passed to it to the proxy baseUri and retain any subpaths in the baseUri regardless of whether the path begins with a slash or not. The use case for the SpaProxy in the Angular Spa template means that it will always receive paths that start with a trailing slash.
There is also a side effect, if SpaProxy is used inside app.Map("/app", ...) , a request for "/app" will be sent to the proxy as empty string path as app.Map removes the matched path segment. (observed in testing) So in this instance the SpaProxy baseUri sub-path will be retained because the path passed to SpaProxy is an empty string, however if the request is for "/app/test" then the baseUri sub-path is removed because the request has been changed to "/test" which starts with a slash.
Here are the results of testing using new Uri(baseUri, relativeUri) with a combination of different baseUri and relativeUri paths:
// Tests with trailing slash on baseUri
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "/");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/" // as per the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "/test");
logger.LogInformation($"targetUri: {targetUri}");
"http://localhost:8080/test" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/app/test" // matches the docs
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/app/" // matches the docs?
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "http://example.com/test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://example.com/test" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app/"), "//example.com/test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://example.com/test" // wow! what the heck! is this in the docs?
// Tests without trailing slash on baseUri
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "/");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "/test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/test" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/test" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://localhost:8080/app" // matches the docs?
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "http://example.com/test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://example.com/test" // matches the docs but does not feel right
var targetUri = new Uri(new Uri("http://localhost:8080/app"), "//example.com/test");
logger.LogInformation($"targetUri: {targetUri}");
"targetUri: http://example.com/test" // wow! what the heck! is this in the docs?
I find that case of the relativeUri being an absolute path "http://example.com/test" disturbing because it means that SpaProxy will try to forward the request to that host ignoring the baseUri or any security implications in dev mode. How would such a request get through to the middleware in the first place, see the example in the tests above, passing "//example.com/test" as the path is one possible way. Also refer to same issue here: https://github.com/aspnet/JavaScriptServices/issues/1763
Please fix the potential security issue and consider changing the behaviour of SpaProxy to enable proxying to subpaths in the Angular Spa template, or update the docs to say that this is not possible.