tgitchel
tgitchel

Reputation: 51

C# .NET Code to validate WSO2 API Gateway JWT signature, using SHA256withRSA algorithm

Can someone provide sample C# .NET code to validate the JWT issued by WSO2 API Gateway, which is signed using SHA256withRSA algorithm. I'm pretty sure I need to set the TokenValidationParameters.IssuerSigningToken, and then call JwtSecurityTokenHandler.ValidateToken method, but I haven't been able to get it to work, or find any sample code.

This is what I have so far:

 // Use JwtSecurityTokenHandler to validate the JWT token
 var tokenHandler = new JwtSecurityTokenHandler();
 var convertedSecret = EncodeSigningToken(ConfigurationManager.AppSettings["ClientSecret"]);

 // Read the JWT
 var parsedJwt = tokenHandler.ReadToken(token);


 // Set the expected properties of the JWT token in the TokenValidationParameters
 var validationParameters = new TokenValidationParameters()
 {
     NameClaimType = "http://wso2.org/claims/enduser",
     AuthenticationType = "http://wso2.org/claims/usertype",
     ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
     ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
     IssuerSigningToken = new BinarySecretSecurityToken(convertedSecret)
 };


 var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);

Upvotes: 1

Views: 1473

Answers (2)

Ashish Mehta
Ashish Mehta

Reputation: 81

WSO2 provides an option to change the format of JWT to be URL Encoded, after which custom code will not be required.

Documentation @ https://docs.wso2.com/display/AM260/Passing+Enduser+Attributes+to+the+Backend+Using+JWT mentions:

"However, for certain apps you might need to have it in Base64URL encoding. To encode the JWT using Base64URL encoding, add the URLSafeJWTGenerator class in the element in the /repository/conf/api-manager.xml"

Upvotes: 0

tgitchel
tgitchel

Reputation: 51

The JWT from the WSO2 API Gateway does not follow the specification (https://www.rfc-editor.org/rfc/rfc7519).

All the samples I have seen are of the form:

<Base64lEncodedHeader>.<Base64EncodedPayload>.<OPTIONAL, Base64EncodedSignature>

but should be:

<Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>.<OPTIONAL, Base64UrlEncodedSignature>

The problem is the use of Base64 instead of Base64Url encoding. Since the signature is based on <Base64EncodedHeader>.<Base64EncodedPayload>, and the MS JWT framework is validating the signature against the expected <Base64UrlEncodedHeader>.<Base64UrlEncodedPayload>, it will always fail validation. I had to write my own custom signature verification code to work around this problem. Then I strip off the signature from the token prior to parsing and decoding with JwtSecurityTokenHandler.

Here is the final code:

try
{
    // Get data and signature from unaltered token
    var data = Encoding.UTF8.GetBytes(token.Split('.')[0] + '.' + token.Split('.')[1]);
    var signature = Convert.FromBase64String(token.Split('.')[2]);

    // Get certificate from file
    var x509 = new X509Certificate2(HttpContext.Current.Server.MapPath("~/App_Data/" + ConfigurationManager.AppSettings["CertFileName"]));

    // Verify the data with the signature
    var csp = (RSACryptoServiceProvider)x509.PublicKey.Key;
    if (!csp.VerifyData(data, "SHA256", signature))
    {
        // Signature verification failed; data was possibly altered
        throw new SecurityTokenValidationException("Data signature verification failed. Token cannot be trusted!");
    }

    // strip off signature from token
    token = token.Substring(0, token.LastIndexOf('.') + 1);

    // Convert Base64 encoded token to Base64Url encoding
    token = token.Replace('+', '-').Replace('/', '_').Replace("=", "");

    // Use JwtSecurityTokenHandler to validate the JWT token
    var tokenHandler = new JwtSecurityTokenHandler();

    // Read the JWT
    var parsedJwt = tokenHandler.ReadToken(token);

    // Set the expected properties of the JWT token in the TokenValidationParameters
    var validationParameters = new TokenValidationParameters()
    {
        NameClaimType = "http://wso2.org/claims/enduser",
        AuthenticationType = ((JwtSecurityToken)parsedJwt).Claims.Where(c => c.Type == "http://wso2.org/claims/usertype").First().Value,
        ValidateAudience = false,
        ValidateLifetime = true,
        ValidateIssuer = true,
        ValidateIssuerSigningKey = false,
        RequireExpirationTime = true,
        RequireSignedTokens = false,
        //ValidAudience = ConfigurationManager.AppSettings["AllowedAudience"],
        ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
        //IssuerSigningToken = new X509SecurityToken(cert),
        CertificateValidator = X509CertificateValidator.None
    };

    // Set both HTTP Context and Thread principals, so they will be in sync
    HttpContext.Current.User = tokenHandler.ValidateToken(token, validationParameters, out parsedJwt);
    Thread.CurrentPrincipal = HttpContext.Current.User;

    // Treat as ClaimsPrincipal, extract JWT expiration and inject it into request headers
    var cp = (ClaimsPrincipal)Thread.CurrentPrincipal;
    context.Request.Headers.Add("JWT-Expiration", cp.FindFirst("exp").Value);
}
catch (SecurityTokenValidationException stvErr)
{
    // Log error
    if (context.Trace.IsEnabled)
        context.Trace.Write("JwtAuthorization", "Error validating token.", stvErr);
}
catch (System.Exception ex)
{
    // Log error
    if (context.Trace.IsEnabled)
        context.Trace.Write("JwtAuthorization", "Error parsing token.", ex);
}

Upvotes: 3

Related Questions