C# – Dynamically append OWIN JWT resource server Application clients (audiences)

cjwtoauthowin

I have a C# API that uses OWIN JWT for authentication.

My startup.cs (of my resource server) configures OAuth vis the code:

public void ConfigureOAuth(IAppBuilder app)
{
    var issuer = "<the_same_issuer_as_AuthenticationServer.Api>";

    // Api controllers with an [Authorize] attribute will be validated with JWT
    var audiences = DatabaseAccessLayer.GetAllowedAudiences(); // Gets a list of audience Ids, secrets, and names (although names are unused)

    // List the 
    List<string> audienceId = new List<string>();
    List<IIssuerSecurityTokenProvider> providers = new List<IIssuerSecurityTokenProvider>();
    foreach (var aud in audiences) {
        audienceId.Add(aud.ClientId);
        providers.Add(new SymmetricKeyIssuerSecurityTokenProvider(issuer, TextEncodings.Base64Url.Decode(aud.ClientSecret)));
    }

    app.UseJwtBearerAuthentication(
        new JwtBearerAuthenticationOptions
        {
            AuthenticationMode = AuthenticationMode.Active,
            AllowedAudiences = audienceId.ToArray(),
            IssuerSecurityTokenProviders = providers.ToArray(),
            Provider = new OAuthBearerAuthenticationProvider
            {
                OnValidateIdentity = context =>
                {
                    context.Ticket.Identity.AddClaim(new System.Security.Claims.Claim("newCustomClaim", "newValue"));
                    return Task.FromResult<object>(null);
                }
            }
        });
}

which allows authenticated bearer tokens to be checked agains multiple ClientIDs. This works well.
However, my web application allows for a user to create a new Application audience (i.e., a new ClientID, ClientSecret, and ClientName combination), but after this happens, I don't know how to get the resource server's JwtBearerAuthenticationOptions to recognize the newly created audience.

I can restart the server after a new audience so that ConfigureOAuth() reruns after, but this is not a good approach in the long run.

Does anyone have any idea how to add audiences (i.e., a new **ClientID, ClientSecret, and ClientName combination) to the OWIN application JwtBearerAuthenticationOptions outside of startup.cs and ConfigureOAuth()?**

I have been looking to: https://docs.auth0.com/aspnetwebapi-owin-tutorial and http://bitoftech.net/2014/10/27/json-web-token-asp-net-web-api-2-jwt-owin-authorization-server/ for help, but both code examples display the same issue described above.

Best Answer

The following works when using the X509CertificateSecurityTokenProvider. It has been modified to use the SymmetricKeyIssuerSecurityTokenProvider but has not been yet been tested.

public void ConfigureOAuth(IAppBuilder app)
{
    var issuer = "<the_same_issuer_as_AuthenticationServer.Api>";

    // Api controllers with an [Authorize] attribute will be validated with JWT
    Func<IEnumerable<Audience>> allowedAudiences = () => DatabaseAccessLayer.GetAllowedAudiences();

    var bearerOptions = new OAuthBearerAuthenticationOptions
    {
        AccessTokenFormat = new JwtFormat(new TokenValidationParameters
        {
            AudienceValidator = (audiences, securityToken, validationParameters) =>
            {
                return allowedAudiences().Select(x => x.ClientId).Intersect(audiences).Count() > 0;
            },
            ValidIssuers = new ValidIssuers { Audiences = allowedAudiences },
            IssuerSigningTokens = new SecurityTokensTokens(issuer) { Audiences = allowedAudiences }
        })
    };
    app.UseOAuthBearerAuthentication(bearerOptions);
}

public abstract class AbstractAudiences<T> : IEnumerable<T>
{
    public Func<IEnumerable<Audience>> Audiences { get; set; }

    public abstract IEnumerator<T> GetEnumerator();

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

public class SecurityTokensTokens : AbstractAudiences<SecurityToken>
{
    private string issuer;

    public SecurityTokensTokens(string issuer)
    {
        this.issuer = issuer;
    }

    public override IEnumerator<SecurityToken> GetEnumerator()
    {
        foreach (var aud in Audiences())
        {
            foreach (var securityToken in new SymmetricKeyIssuerSecurityTokenProvider(issuer, TextEncodings.Base64Url.Decode(aud.ClientSecret)).SecurityTokens)
            {
                yield return securityToken;
            };
        }
    }
}

public class ValidIssuers : AbstractAudiences<string>
{
    public override IEnumerator<string> GetEnumerator()
    {
        foreach (var aud in Audiences())
        {
            yield return aud.ClientSecret;
        }
    }
}

}

Related Topic