JWT with multiple Audiences

Apr 21, 2014 at 10:09 PM
Hi,

today, I've discovered one more time, that Katana can not deal with JWTs that have been created for multiple audiences. I this case, just one of thouse audiences has to match an expected audience. But the underlying JWT-Handler does not respect this.

Supporting JWTs with multiple audiences is IMHO very importat, for instance, cause the current draft of the Assertion Framework for OAuth 2.0 (http://tools.ietf.org/html/draft-ietf-oauth-assertions-15) damands, that the auth-server is an audience and a JWT where the auth-server is the only audience is not of much use.

Therefore I'm wondering, if you are going to Change this?

Wishes,
Manfred
Apr 22, 2014 at 1:15 PM
Hey,

JwtFormat has been designed to support multiple audiences and has a specific constructor for that:
/// <summary>
/// Initializes a new instance of the <see cref="JwtFormat"/> class.
/// </summary>
/// <param name="allowedAudiences">The allowed audience for JWTs.</param>
/// <param name="issuerCredentialProviders">The issuer credential provider.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the <paramref name="issuerCredentialProviders"/> is null.</exception>
public JwtFormat(IEnumerable<string> allowedAudiences, IEnumerable<IIssuerSecurityTokenProvider> issuerCredentialProviders)
{
    if (allowedAudiences == null)
    {
        throw new ArgumentNullException("allowedAudiences");
    }

    var audiences = new List<string>(allowedAudiences);
    if (!audiences.Any())
    {
        throw new ArgumentOutOfRangeException("allowedAudiences", Properties.Resources.Exception_AudiencesMustBeSpecified);
    }

    _allowedAudiences.AddRange(audiences);

    if (issuerCredentialProviders == null)
    {
        throw new ArgumentNullException("issuerCredentialProviders");
    }

    var credentialProviders = new List<IIssuerSecurityTokenProvider>(issuerCredentialProviders);
    if (!credentialProviders.Any())
    {
        throw new ArgumentOutOfRangeException("issuerCredentialProviders", Properties.Resources.Exception_IssuerCredentialProvidersMustBeSpecified);
    }

    foreach (var issuerCredentialProvider in credentialProviders)
    {
        _issuerCredentialProviders.Add(issuerCredentialProvider.Issuer, issuerCredentialProvider);
    }

    ValidateIssuer = true;
}
This constructor is used by the UseJwtBearerAuthentication extension, which takes a JwtBearerAuthenticationOptions parameter that allows you to configure your audiences (see the AllowedAudiences property).
Apr 22, 2014 at 1:30 PM
Edited Apr 22, 2014 at 1:33 PM
Hi,

thx for your reply. It's true, that one can specify several allowed audiences, but when the token has an aud-claim with several audiences, validation fails. That's because, the used JWT Handler compares all allowed audiences directly with the aud-claim, which is a json-array, when there is more then one value.

It does somethink like:
ok = false
for all allowed audiences a
    if token.aud == a then ok = true
This fails, when token.aud has more than one value, cause in this case, it it's content is something like "['firstAud', 'secondAud', 'thirdAud'].

Instead of this, the JWT Handler should do something like:
ok = false
for all allowed audiences a
    for all audiences b in token.aud
         if b == a then ok = true
Wishes
Manfred
Apr 22, 2014 at 2:00 PM
Edited Apr 22, 2014 at 2:01 PM
Oh I see, I thought your concern was purely related to the Katana JwtFormat class, sorry.

The problem is that the specifications concerning the audience claim have changed since the first draft:
The "aud" (audience) claim identifies the audience that the JWT is
intended for. The principal intended to process the JWT MUST be
identified with the value of the audience claim. If the principal
processing the claim does not identify itself with the identifier in
the "aud" claim value then the JWT MUST be rejected. The
interpretation of the audience value is generally application
specific. The "aud" value is case sensitive. Its value MUST be a
string containing a StringOrURI value. This claim is OPTIONAL.
http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-01#section-4.1.5
The "aud" (audience) claim identifies the recipients that the JWT is
intended for. Each principal intended to process the JWT MUST
identify itself with a value in the audience claim. If the principal
processing the claim does not identify itself with a value in the
"aud" claim when this claim is present, then the JWT MUST be
rejected. In the general case, the "aud" value is an array of case-
sensitive strings, each containing a StringOrURI value. In the
special case when the JWT has one audience, the "aud" value MAY be a
single case-sensitive string containing a StringOrURI value. The
interpretation of audience values is generally application specific.
Use of this claim is OPTIONAL.
http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-19#section-4.1.3

It seems that the JwtSecurityTokenHandler class has not been updated to include these new changes and the fact that the audience claim can now be an array :
protected virtual void ValidateAudience(JwtSecurityToken jwt, TokenValidationParameters validationParameters)
{
    if (validationParameters.AllowedAudiences != null)
    {
        foreach (string current in validationParameters.AllowedAudiences)
        {
            if (string.Equals(current, jwt.Audience, StringComparison.Ordinal))
            {
                return;
            }
        }
    }
    throw new AudienceUriValidationFailedException(string.Format(CultureInfo.InvariantCulture, "Jwt10303: Audience validation failed. jwt.Audience: '{0}'. Could not match:  validationParameters.AllowedAudience: '{1}' and validationParameters.AllowedAudiences: '{2}'", new object[]
    {
        jwt.Audience,
        validationParameters.AllowedAudience ?? "null",
        Utility.SerializeAsSingleCommaDelimitedString(validationParameters.AllowedAudiences)
    }));
}
That said, the JwtSecurityTokenHandler class is not sealed and the mentioned method can be easily overridden to implement the correct check.
I doubt you'll get useful feedback here, as the JwtSecurityTokenHandler is probably not managed by the Katana team. I suggest pinging Vittorio Bertocci on Twitter ;)
Apr 22, 2014 at 2:20 PM
Hi,

thx for your reply. That was excactly what I did when I faced this problem last time in decemeber. But the problem was (and still is) that JwtFormat directly instantiates the JwtSecurityTokenHandler within Protect. That prevents from exchanging it for a sub-class. That's why I had to write a patched version of JwtFormat that re-implemented Protect.

Perhaps, if I find some time, I write a pull request for this ...

Wishes,
Manfred
Developer
Apr 30, 2014 at 5:44 PM
Manfred,

We are going to make some fixes here that will allow users the ability to set a JWTSTH on the Options. Thatcher fork has some work on this.
Coordinator
Apr 30, 2014 at 5:53 PM
My changes for making JWTSTH settable are already in the dev branch. Let me know what you think.
Developer
Apr 30, 2014 at 6:01 PM
Nice, even better.
Apr 30, 2014 at 11:42 PM
Great! That really helps! Thx!!!