Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate the ValidIssuer and the ValidAudience properties of the JwtBearerOptions.TokenValidationParameters. #52821

Merged
merged 9 commits into from
Feb 22, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,8 @@ public void Configure(string? name, JwtBearerOptions options)

var issuer = configSection[nameof(TokenValidationParameters.ValidIssuer)];
var issuers = configSection.GetSection(nameof(TokenValidationParameters.ValidIssuers)).GetChildren().Select(iss => iss.Value).ToList();
if (issuer is not null)
{
issuers.Add(issuer);
}
var audience = configSection[nameof(TokenValidationParameters.ValidAudience)];
var audiences = configSection.GetSection(nameof(TokenValidationParameters.ValidAudiences)).GetChildren().Select(aud => aud.Value).ToList();
if (audience is not null)
{
audiences.Add(audience);
}

options.Authority = configSection[nameof(options.Authority)] ?? options.Authority;
options.BackchannelTimeout = StringHelpers.ParseValueOrDefault(configSection[nameof(options.BackchannelTimeout)], _invariantTimeSpanParse, options.BackchannelTimeout);
Expand All @@ -73,8 +65,10 @@ public void Configure(string? name, JwtBearerOptions options)
{
ValidateIssuer = issuers.Count > 0,
ValidIssuers = issuers,
ValidIssuer = issuer,
ValidateAudience = audiences.Count > 0,
ValidAudiences = audiences,
ValidAudience = audience,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = GetIssuerSigningKeys(configSection, issuers),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public async Task WebApplicationBuilder_RegistersAuthenticationAndAuthorizationM
Assert.True(app.Properties.ContainsKey("__AuthorizationMiddlewareSet"));

var options = app.Services.GetService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(new[] { "SomeIssuer" }, options.TokenValidationParameters.ValidIssuers);
Assert.Equal("SomeIssuer", options.TokenValidationParameters.ValidIssuer);
Assert.Equal(new[] { "https://localhost:5001" }, options.TokenValidationParameters.ValidAudiences);
}

Expand Down
69 changes: 0 additions & 69 deletions src/Security/Authentication/test/JwtBearerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1010,75 +1010,6 @@ public async Task ExpirationAndIssuedWhenMinOrMaxValue()
Assert.Equal(max, elementValueUtc);
}

[Fact]
public void CanReadJwtBearerOptionsFromConfig()
{
var services = new ServiceCollection().AddLogging();
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidIssuer", "dotnet-user-jwts"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5000"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:1", "https://localhost:5001"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:BackchannelTimeout", "00:01:00"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:RequireHttpsMetadata", "false"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SaveToken", "True"),
}).Build();
services.AddSingleton<IConfiguration>(config);

// Act
var builder = services.AddAuthentication(o =>
{
o.AddScheme<TestHandler>("Bearer", "Bearer");
});
builder.AddJwtBearer("Bearer", o => o.UseSecurityTokenValidators = true);
RegisterAuth(builder, _ => { });
var sp = services.BuildServiceProvider();

// Assert
var jwtBearerOptions = sp.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(jwtBearerOptions.TokenValidationParameters.ValidIssuers, new[] { "dotnet-user-jwts" });
Assert.Equal(jwtBearerOptions.TokenValidationParameters.ValidAudiences, new[] { "http://localhost:5000", "https://localhost:5001" });
Assert.Equal(jwtBearerOptions.BackchannelTimeout, TimeSpan.FromSeconds(60));
Assert.False(jwtBearerOptions.RequireHttpsMetadata);
Assert.True(jwtBearerOptions.SaveToken);
Assert.True(jwtBearerOptions.MapInboundClaims); // Assert default values are respected
}

[Fact]
public void CanReadMultipleIssuersFromConfig()
{
var services = new ServiceCollection().AddLogging();
var firstKey = "qPG6tDtfxFYZifHW3sEueQ==";
var secondKey = "6JPzXj6aOPdojlZdeLshaA==";
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidIssuers:0", "dotnet-user-jwts"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidIssuers:1", "dotnet-user-jwts-2"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:0:Issuer", "dotnet-user-jwts"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:0:Value", firstKey),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:0:Length", "32"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:1:Issuer", "dotnet-user-jwts-2"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:1:Value", secondKey),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SigningKeys:1:Length", "32"),
}).Build();
services.AddSingleton<IConfiguration>(config);

// Act
var builder = services.AddAuthentication(o =>
{
o.AddScheme<TestHandler>("Bearer", "Bearer");
});
builder.AddJwtBearer("Bearer", o => o.UseSecurityTokenValidators = true);
RegisterAuth(builder, _ => { });
var sp = services.BuildServiceProvider();

// Assert
var jwtBearerOptions = sp.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(2, jwtBearerOptions.TokenValidationParameters.IssuerSigningKeys.Count());
Assert.Equal(firstKey, Convert.ToBase64String(jwtBearerOptions.TokenValidationParameters.IssuerSigningKeys.OfType<SymmetricSecurityKey>().FirstOrDefault()?.Key));
Assert.Equal(secondKey, Convert.ToBase64String(jwtBearerOptions.TokenValidationParameters.IssuerSigningKeys.OfType<SymmetricSecurityKey>().LastOrDefault()?.Key));
}

class InvalidTokenValidator : ISecurityTokenValidator
{
public InvalidTokenValidator()
Expand Down
38 changes: 33 additions & 5 deletions src/Security/Authentication/test/JwtBearerTests_Handler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -968,8 +968,9 @@ public void CanReadJwtBearerOptionsFromConfig()
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidIssuer", "dotnet-user-jwts"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5000"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:1", "https://localhost:5001"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidIssuers:0", "dotnet-user-jwts-2"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudience", "http://localhost:5000"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5001"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:BackchannelTimeout", "00:01:00"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:RequireHttpsMetadata", "false"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:SaveToken", "True"),
Expand All @@ -987,14 +988,41 @@ public void CanReadJwtBearerOptionsFromConfig()

// Assert
var jwtBearerOptions = sp.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(jwtBearerOptions.TokenValidationParameters.ValidIssuers, new[] { "dotnet-user-jwts" });
Assert.Equal(jwtBearerOptions.TokenValidationParameters.ValidAudiences, new[] { "http://localhost:5000", "https://localhost:5001" });
Assert.Equal(jwtBearerOptions.BackchannelTimeout, TimeSpan.FromSeconds(60));
Assert.Equal("dotnet-user-jwts", jwtBearerOptions.TokenValidationParameters.ValidIssuer);
Assert.Equal(["dotnet-user-jwts-2"], jwtBearerOptions.TokenValidationParameters.ValidIssuers);
Assert.Equal("http://localhost:5000", jwtBearerOptions.TokenValidationParameters.ValidAudience);
Assert.Equal(["http://localhost:5001"], jwtBearerOptions.TokenValidationParameters.ValidAudiences);
Assert.Equal(TimeSpan.FromSeconds(60), jwtBearerOptions.BackchannelTimeout);
Assert.False(jwtBearerOptions.RequireHttpsMetadata);
Assert.True(jwtBearerOptions.SaveToken);
Assert.True(jwtBearerOptions.MapInboundClaims); // Assert default values are respected
}

[Fact]
public void CanReadMultipleAudiencesFromConfig()
{
var services = new ServiceCollection().AddLogging();
var config = new ConfigurationBuilder().AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:0", "http://localhost:5000"),
new KeyValuePair<string, string>("Authentication:Schemes:Bearer:ValidAudiences:1", "https://localhost:5001")
}).Build();
services.AddSingleton<IConfiguration>(config);

// Act
var builder = services.AddAuthentication(o =>
{
o.AddScheme<TestHandler>("Bearer", "Bearer");
});
builder.AddJwtBearer("Bearer");
RegisterAuth(builder, _ => { });
var sp = services.BuildServiceProvider();

// Assert
var jwtBearerOptions = sp.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().Get(JwtBearerDefaults.AuthenticationScheme);
Assert.Equal(["http://localhost:5000", "https://localhost:5001"], jwtBearerOptions.TokenValidationParameters.ValidAudiences);
}

[Fact]
public void CanReadMultipleIssuersFromConfig()
{
Expand Down
Loading