Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions identity-server/nuget.config

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,14 @@ public async Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secr
// Read the token so we can get the "typ" header value if it exists.
var handlerForHeader = new JsonWebTokenHandler();
var tokenForHeader = handlerForHeader.ReadJsonWebToken(jwtTokenString);
var jwtTyp = tokenForHeader.GetHeaderValue<string>("typ");

// If strict mode is not enabled by option but the "typ" header value "client-authentication+jwt" is provided,
// enforce strict audience validation.
if (string.Equals(jwtTyp, "client-authentication+jwt", StringComparison.OrdinalIgnoreCase))
if (tokenForHeader.TryGetHeaderValue<string>("typ", out var jwtTyp))
{
enforceStrictAud = true;
if (string.Equals(jwtTyp, "client-authentication+jwt", StringComparison.OrdinalIgnoreCase))
{
enforceStrictAud = true;
}
}
}
catch (Exception ex)
Expand Down Expand Up @@ -252,4 +253,4 @@ private bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, string va

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,24 @@ public PrivateKeyJwtSecretValidation()
_clients = new InMemoryClientStore(ClientValidationTestClients.Get());
}

private JwtSecurityToken CreateToken(string clientId, string aud = "https://idsrv.com/", DateTime? nowOverride = null, bool setTyp = false)
private JwtSecurityToken CreateToken(string clientId, string aud = "https://idsrv.com/", DateTime? nowOverride = null, Typ typ = Typ.None)
{
return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, aud), nowOverride, setTyp);
return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, aud), nowOverride, typ);
}

private JwtSecurityToken CreateToken(string clientId, string[] audiences, DateTime? nowOverride = null, bool setTyp = false)
private JwtSecurityToken CreateToken(string clientId, string[] audiences, DateTime? nowOverride = null, Typ typ = Typ.None)
{
return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, JsonSerializer.Serialize(audiences), JsonClaimValueTypes.JsonArray), nowOverride, setTyp);
return CreateTokenHelper(clientId, new Claim(JwtClaimTypes.Audience, JsonSerializer.Serialize(audiences), JsonClaimValueTypes.JsonArray), nowOverride, typ);
}

private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, DateTime? nowOverride = null, bool setJwtTyp = false)
public enum Typ
{
None,
JWT,
ClientAuthentication
}

private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, DateTime? nowOverride = null, Typ jwtTyp = Typ.None)
{
var certificate = TestCert.Load();
var now = nowOverride ?? DateTime.UtcNow;
Expand All @@ -80,10 +87,18 @@ private JwtSecurityToken CreateTokenHelper(string clientId, Claim aud = null, Da
)
);

if (setJwtTyp)
if (jwtTyp == Typ.ClientAuthentication)
{
token.Header["typ"] = "client-authentication+jwt";
}
else if (jwtTyp == Typ.JWT)
{
token.Header["typ"] = "JWT";
}
else
{
token.Header.Remove("typ");
}

return token;
}
Expand Down Expand Up @@ -144,12 +159,17 @@ public async Task Valid_Certificate_Base64()
}

[Theory]
[InlineData("https://idsrv.com")]
[InlineData("https://idsrv.com/")]
[InlineData("https://idsrv.com/connect/token")]
[InlineData("https://idsrv.com/connect/ciba")]
[InlineData("https://idsrv.com/connect/par")]
public async Task Strict_audience_disabled_and_no_typ_header_allows_legacy_aud_values(string aud)
[InlineData("https://idsrv.com", Typ.None)]
[InlineData("https://idsrv.com/", Typ.None)]
[InlineData("https://idsrv.com/connect/token", Typ.None)]
[InlineData("https://idsrv.com/connect/ciba", Typ.None)]
[InlineData("https://idsrv.com/connect/par", Typ.None)]
[InlineData("https://idsrv.com", Typ.JWT)]
[InlineData("https://idsrv.com/", Typ.JWT)]
[InlineData("https://idsrv.com/connect/token", Typ.JWT)]
[InlineData("https://idsrv.com/connect/ciba", Typ.JWT)]
[InlineData("https://idsrv.com/connect/par", Typ.JWT)]
public async Task Strict_audience_disabled_and_no_typ_header_or_JWT_typ_header_allows_legacy_aud_values(string aud, Typ typ)
{
_options.Preview.StrictClientAssertionAudienceValidation = false;

Expand All @@ -159,7 +179,7 @@ public async Task Strict_audience_disabled_and_no_typ_header_allows_legacy_aud_v
var secret = new ParsedSecret
{
Id = clientId,
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: false)),
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: typ)),
Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer
};

Expand All @@ -185,7 +205,7 @@ public async Task Strict_audience_from_options_validates_audience(string aud, bo
var secret = new ParsedSecret
{
Id = clientId,
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: true)),
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: Typ.ClientAuthentication)),
Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer
};

Expand All @@ -211,7 +231,7 @@ public async Task Strict_audience_from_typ_header_validates_audience(string aud,
var secret = new ParsedSecret
{
Id = clientId,
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, setTyp: true)),
Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, aud: aud, typ: Typ.ClientAuthentication)),
Type = IdentityServerConstants.ParsedSecretTypes.JwtBearer
};

Expand All @@ -221,11 +241,13 @@ public async Task Strict_audience_from_typ_header_validates_audience(string aud,
}

[Theory]
[InlineData(true, true, false)]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
[InlineData(false, false, true)]
public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTyp, bool setStrictOption, bool expectedResult)
[InlineData(Typ.ClientAuthentication, true, false)]
[InlineData(Typ.ClientAuthentication, false, false)]
[InlineData(Typ.None, true, false)]
[InlineData(Typ.None, false, true)]
[InlineData(Typ.JWT, true, false)]
[InlineData(Typ.JWT, false, true)]
public async Task Strict_audience_does_not_allow_single_valued_arrays(Typ typ, bool setStrictOption, bool expectedResult)
{
_options.Preview.StrictClientAssertionAudienceValidation = setStrictOption;

Expand All @@ -234,7 +256,7 @@ public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTy
var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(
clientId,
audiences: ["https://idsrv.com/connect/token"],
setTyp: setTyp));
typ: typ));

var secret = new ParsedSecret
{
Expand All @@ -249,11 +271,13 @@ public async Task Strict_audience_does_not_allow_single_valued_arrays(bool setTy
}

[Theory]
[InlineData(true, true, false)]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
[InlineData(false, false, true)]
public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp, bool setStrictOption, bool expectedResult)
[InlineData(Typ.ClientAuthentication, true, false)]
[InlineData(Typ.ClientAuthentication, false, false)]
[InlineData(Typ.None, true, false)]
[InlineData(Typ.None, false, true)]
[InlineData(Typ.JWT, true, false)]
[InlineData(Typ.JWT, false, true)]
public async Task Strict_audience_does_not_allow_multi_valued_arrays(Typ typ, bool setStrictOption, bool expectedResult)
{
_options.Preview.StrictClientAssertionAudienceValidation = setStrictOption;

Expand All @@ -262,7 +286,7 @@ public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp
var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(
clientId,
audiences: ["https://idsrv.com", "https://idsrv.com/"],
setTyp: setTyp));
typ: typ));

var secret = new ParsedSecret
{
Expand All @@ -277,17 +301,19 @@ public async Task Strict_audience_does_not_allow_multi_valued_arrays(bool setTyp
}

[Theory]
[InlineData(true, true, true)]
[InlineData(true, false, true)]
[InlineData(false, true, false)]
[InlineData(false, false, true)]
public async Task Strict_audience_only_allows_correct_type(bool setTyp, bool enforceStrict, bool expectedResult)
[InlineData(Typ.ClientAuthentication, true, true)]
[InlineData(Typ.ClientAuthentication, false, true)]
[InlineData(Typ.None, true, false)]
[InlineData(Typ.None, false, true)]
[InlineData(Typ.JWT, true, false)]
[InlineData(Typ.JWT, false, true)]
public async Task Strict_audience_only_allows_correct_type(Typ typ, bool enforceStrict, bool expectedResult)
{
_options.Preview.StrictClientAssertionAudienceValidation = enforceStrict;

var clientId = "certificate_base64_valid";
var client = await _clients.FindEnabledClientByIdAsync(clientId);
var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, setTyp: setTyp));
var token = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId, typ: typ));

var secret = new ParsedSecret
{
Expand Down Expand Up @@ -419,4 +445,4 @@ public async Task Invalid_Unsigned_Token()

result.Success.Should().BeFalse();
}
}
}