Skip to content

Commit 0a3e89d

Browse files
adamintJamesNK
andauthored
Redirect to homepage if login page is requested when token auth is not enabled (#5123)
* Redirect to homepage if login page is requested when token auth is not enabled * add ValidateTokenMiddleware tests * fix test names * remove nsubstitute * remove unsecured testing changes * Update src/Aspire.Dashboard/Model/ValidateTokenMiddleware.cs --------- Co-authored-by: James Newton-King <[email protected]>
1 parent 9d0bc81 commit 0a3e89d

File tree

5 files changed

+120
-14
lines changed

5 files changed

+120
-14
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<PackageVersion Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion)" />
5858
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(MicrosoftAspNetCoreOpenApiPackageVersion)" />
5959
<PackageVersion Include="Microsoft.AspNetCore.OutputCaching.StackExchangeRedis" Version="$(MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion)" />
60+
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="$(MicrosoftAspNetCoreTestHostPackageVersion)" />
6061
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="$(MicrosoftExtensionsCachingStackExchangeRedisPackageVersion)" />
6162
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="$(MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePackageVersion)" />
6263
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="$(MicrosoftExtensionsDiagnosticsHealthChecksPackageVersion)" />

eng/Versions.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>8.0.7</MicrosoftAspNetCoreAuthenticationOpenIdConnectPackageVersion>
5050
<MicrosoftAspNetCoreOpenApiPackageVersion>8.0.7</MicrosoftAspNetCoreOpenApiPackageVersion>
5151
<MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion>8.0.7</MicrosoftAspNetCoreOutputCachingStackExchangeRedisPackageVersion>
52+
<MicrosoftAspNetCoreTestHostPackageVersion>8.0.7</MicrosoftAspNetCoreTestHostPackageVersion>
5253
<MicrosoftExtensionsCachingStackExchangeRedisPackageVersion>8.0.7</MicrosoftExtensionsCachingStackExchangeRedisPackageVersion>
5354
<MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePackageVersion>8.0.7</MicrosoftExtensionsDiagnosticsHealthChecksEntityFrameworkCorePackageVersion>
5455
<MicrosoftExtensionsDiagnosticsHealthChecksPackageVersion>8.0.7</MicrosoftExtensionsDiagnosticsHealthChecksPackageVersion>

src/Aspire.Dashboard/Model/ValidateTokenMiddleware.cs

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,21 @@ public ValidateTokenMiddleware(RequestDelegate next, IOptionsMonitor<DashboardOp
2626

2727
public async Task InvokeAsync(HttpContext context)
2828
{
29-
if (context.Request.Path.Equals("/login", StringComparisons.UrlPath) && context.Request.Query.TryGetValue("t", out var value))
29+
if (context.Request.Path.Equals("/login", StringComparisons.UrlPath))
3030
{
31-
if (_options.CurrentValue.Frontend.AuthMode == FrontendAuthMode.BrowserToken)
31+
if (_options.CurrentValue.Frontend.AuthMode != FrontendAuthMode.BrowserToken)
32+
{
33+
_logger.LogDebug($"Request to validate token URL but auth mode isn't set to {FrontendAuthMode.BrowserToken}.");
34+
35+
RedirectAfterValidation(context);
36+
}
37+
else if (context.Request.Query.TryGetValue("t", out var value) && _options.CurrentValue.Frontend.AuthMode == FrontendAuthMode.BrowserToken)
3238
{
3339
var dashboardOptions = context.RequestServices.GetRequiredService<IOptionsMonitor<DashboardOptions>>();
3440
if (await TryAuthenticateAsync(value.ToString(), context, dashboardOptions).ConfigureAwait(false))
3541
{
3642
// Success. Redirect to the app.
37-
if (context.Request.Query.TryGetValue("returnUrl", out var returnUrl))
38-
{
39-
context.Response.Redirect(returnUrl.ToString());
40-
}
41-
else
42-
{
43-
context.Response.Redirect(DashboardUrls.ResourcesUrl());
44-
}
43+
RedirectAfterValidation(context);
4544
}
4645
else
4746
{
@@ -62,15 +61,23 @@ public async Task InvokeAsync(HttpContext context)
6261

6362
return;
6463
}
65-
else
66-
{
67-
_logger.LogDebug($"Request to validate token URL but auth mode isn't set to {FrontendAuthMode.BrowserToken}.");
68-
}
6964
}
7065

7166
await _next(context).ConfigureAwait(false);
7267
}
7368

69+
private static void RedirectAfterValidation(HttpContext context)
70+
{
71+
if (context.Request.Query.TryGetValue("returnUrl", out var returnUrl))
72+
{
73+
context.Response.Redirect(returnUrl.ToString());
74+
}
75+
else
76+
{
77+
context.Response.Redirect(DashboardUrls.ResourcesUrl());
78+
}
79+
}
80+
7481
public static async Task<bool> TryAuthenticateAsync(string incomingBrowserToken, HttpContext httpContext, IOptionsMonitor<DashboardOptions> dashboardOptions)
7582
{
7683
if (string.IsNullOrEmpty(incomingBrowserToken) || dashboardOptions.CurrentValue.Frontend.GetBrowserTokenBytes() is not { } expectedBrowserTokenBytes)

tests/Aspire.Dashboard.Tests/Aspire.Dashboard.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<ItemGroup>
1616
<PackageReference Include="Grpc.Tools" />
1717
<PackageReference Include="Microsoft.DotNet.XUnitExtensions" />
18+
<PackageReference Include="Microsoft.AspNetCore.TestHost" />
1819

1920
<ProjectReference Include="..\..\src\Aspire.Dashboard\Aspire.Dashboard.csproj" />
2021

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Dashboard.Configuration;
5+
using Aspire.Dashboard.Model;
6+
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Hosting;
8+
using Microsoft.AspNetCore.TestHost;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Hosting;
11+
using Xunit;
12+
13+
namespace Aspire.Dashboard.Tests.Middleware;
14+
15+
public class ValidateTokenMiddlewareTests
16+
{
17+
[Fact]
18+
public async Task ValidateToken_NotBrowserTokenAuth_RedirectedToHomepage()
19+
{
20+
using var host = await SetUpHostAsync(FrontendAuthMode.Unsecured, string.Empty);
21+
var response = await host.GetTestClient().GetAsync("/login?t=test");
22+
Assert.Equal("/", response.Headers.Location?.OriginalString);
23+
}
24+
25+
[Fact]
26+
public async Task ValidateToken_NotBrowserTokenAuth_RedirectedToReturnUrl()
27+
{
28+
using var host = await SetUpHostAsync(FrontendAuthMode.Unsecured, string.Empty);
29+
var response = await host.GetTestClient().GetAsync("/login?t=test&returnUrl=/test");
30+
Assert.Equal("/test", response.Headers.Location?.OriginalString);
31+
}
32+
33+
[Fact]
34+
public async Task ValidateToken_BrowserTokenAuth_WrongToken_RedirectsToLogin()
35+
{
36+
using var host = await SetUpHostAsync(FrontendAuthMode.BrowserToken, "token");
37+
var response = await host.GetTestClient().GetAsync("/login?t=wrong");
38+
Assert.Equal("/login", response.Headers.Location?.OriginalString);
39+
}
40+
41+
[Fact]
42+
public async Task ValidateToken_BrowserTokenAuth_WrongToken_RedirectsToLogin_WithReturnUrl()
43+
{
44+
using var host = await SetUpHostAsync(FrontendAuthMode.BrowserToken, "token");
45+
var response = await host.GetTestClient().GetAsync("/login?t=wrong&returnUrl=/test");
46+
Assert.Equal("/login?returnUrl=%2ftest", response.Headers.Location?.OriginalString);
47+
}
48+
49+
[Fact]
50+
public async Task ValidateToken_BrowserTokenAuth_RightToken_RedirectsToHome()
51+
{
52+
using var host = await SetUpHostAsync(FrontendAuthMode.BrowserToken, "token");
53+
var response = await host.GetTestClient().GetAsync("/login?t=token");
54+
Assert.Equal("/", response.Headers.Location?.OriginalString);
55+
}
56+
57+
[Fact]
58+
public async Task ValidateToken_BrowserTokenAuth_RightToken_RedirectsToReturnUrl()
59+
{
60+
using var host = await SetUpHostAsync(FrontendAuthMode.BrowserToken, "token");
61+
var response = await host.GetTestClient().GetAsync("/login?t=token&returnUrl=/test");
62+
Assert.Equal("/test", response.Headers.Location?.OriginalString);
63+
}
64+
65+
private static async Task<IHost> SetUpHostAsync(FrontendAuthMode authMode, string expectedToken)
66+
{
67+
return await new HostBuilder()
68+
.ConfigureWebHost(webBuilder =>
69+
{
70+
webBuilder
71+
.UseTestServer()
72+
.ConfigureServices(services =>
73+
{
74+
services.AddRouting();
75+
services.AddAuthentication().AddCookie();
76+
77+
services.Configure<DashboardOptions>(o =>
78+
{
79+
o.Frontend = new FrontendOptions
80+
{
81+
AuthMode = authMode,
82+
BrowserToken = expectedToken,
83+
EndpointUrls = "http://localhost/" // required for TryParseOptions
84+
};
85+
86+
Assert.True(o.Frontend.TryParseOptions(out _));
87+
});
88+
})
89+
.Configure(app =>
90+
{
91+
app.UseMiddleware<ValidateTokenMiddleware>();
92+
});
93+
})
94+
.StartAsync();
95+
}
96+
}

0 commit comments

Comments
 (0)