Reputation: 51
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
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
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