Blazor Server behind reverse-proxy is case sensitive when app base path is changed
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Describe the bug
I'm trying to host a Blazor Server behind Azure Front Door and noticed issues around case sensitivity for the app base path. Basically I set the base path for ANC using `app.UsePathBase("/foo")` in `Program.cs` and then have a routing rule matching `/foo/*` and `/foo/` in Azure Front Door which will forward to the Blazor server app (hosted on Azure App Services).
Every thing works fine when I navigate to `https://example.com/foo/`. But when I navigate to `https://example.com/FoO/` Blazor will fail to initialize the circuit, and in developer tools I can see the following logs. No exceptions or anything on the server though.
```
[2022-01-12T18:49:30.095Z] Error: The circuit failed to initialize. blazor.server.js:1:115531
[2022-01-12T18:49:30.096Z] Information: Connection disconnected. blazor.server.js:1:36859
Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
```
After adjusting log level on the server side I saw the following log which caught my eye:
```
Received hub invocation: InvocationMessage {
InvocationId: "0",
Target: "StartCircuit",
Arguments: [
https://example.com/foo/,
https://example.com/FoO/,
[{"type":"server","sequence":0,"descriptor":"..."},{"type":"server","sequence":1,"descriptor":"..."}],
], StreamIds: [ ]
}.
```
I think that the case mismatch in `baseUri` (`https://example.com/foo/`) and `uri` (`https://example.com/FoO/`) is because Azure Front Door will forward according to the matching rules (which are case-insensitive). So because the matching rule is `/foo` the forwarded path will be `/foo`. The `uri` I believe is just `location.href` in Blazor JS side.
After following the code I noticed that the case mismatch above will lead to an exception in `NavigationManager`.
```
The URI 'https://example.com/FoO/' is not contained by the base URI 'https://example/foo/'.
```
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Server/src/ComponentHub.cs#L133
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Server/src/Circuits/CircuitFactory.cs#L63
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Components/src/NavigationManager.cs#L161
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Components/src/NavigationManager.cs#L82
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Components/src/NavigationManager.cs#L305
The case mismatch will NOT happen if i navigate directly to the Azure App Service. `StartCircuit` is invoked with arguments `https://example.com/FoO/, https://example.com/FoO/, [....]`
### Expected Behavior
Path should be case-insensitive when hosted behind a reverse-proxy.
### Steps To Reproduce
EDIT: Added local repro code using YARP. See https://github.com/bjorkstromm/aspnetcore-blazor-repro-issue-39469
1. Create new Blazor Server App
2. Add Azure SignalR (needed when using behind Azure Front Door)
a) Install NuGet package `Microsoft.Azure.SignalR`
b) Add `builder.Services.AddSignalR().AddAzureSignalR();` to `Program.cs`
3) Adjust base path
a) `app.UsePathBase("/foo");` to `Program.cs`
4) Publish to Azure App Service
4) Create Azure Front Door and configure
a) Add backend pool, point to Azure App Service
b) Add routing rule with match patterns `/foo/` and `/foo/*` which forwards to backend pool created in previous step.
If above steps are not enough, I could either try creating a minimal repro with Bicep templates, or if possible try to reproduce everything locally using YARP instead of Azure Front Door. Let me know if you need more info.
The root cause however is simple to reproduce (NavigationManager throwing exception):
```csharp
var foo = new FooNavigatioManager();
foo.Init("https://example.com/foo/", "https://example.com/FoO/");
class FooNavigatioManager : NavigationManager
{
public void Init(string baseUri, string uri) => Initialize(baseUri, uri);
}
```
### Exceptions (if any)
```
The URI 'https://example.com/FoO/' is not contained by the base URI 'https://example/foo/'.
```
But it's actually swallowed here:
https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Components/Server/src/ComponentHub.cs#L158
### .NET Version
6.0.100
### Anything else?
Related
https://github.com/dotnet/aspnetcore/issues/23107
https://github.com/dotnet/aspnetcore/issues/6818
Contributor guide
Assessment
This issue has not been assessed yet.