3

I am trying to validate the following test JWT, the chosen key is 'private' and I can successfully verify it on https://jwt.io

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyNzFjNmFkYjNhYTk1YTIxZWI3ZTljMTE2OGViNjI2YiIsImlhdCI6MTQ5MDE5NzQ2MCwibmJmIjoxNDkwMTk3NDYwLCJleHAiOjE0OTAyMDEwNjAsIklwIjoiNzkuMjMxLjczLjE1NCIsIk1lbWJlcklkIjoxfQ.P3m7RkXJ9TUiUFJ2bbtiyoL7OXaD7ITq_LsWMCRJj04

It seems like Microsoft has changed the JwtSecurityTokenhandler() class and the documentation isn't really up to date. I checked some tutorials and gitpages which used new InMemorySymetricSecurityKey() but this class is not even present anymore.

Nuget Package: Install-Package System.IdentityModel.Tokens.Jwt (version 5.1.3).

I have created a simple Console application and I tried to validate the given JWT, but I don't know how I should specify the TokenValidationParameters.

static void Main(string[] args)
{
    var key = "private";
    var jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyNzFjNmFkYjNhYTk1YTIxZWI3ZTljMTE2OGViNjI2YiIsImlhdCI6MTQ5MDE5NzQ2MCwibmJmIjoxNDkwMTk3NDYwLCJleHAiOjE0OTAyMDEwNjAsIklwIjoiNzkuMjMxLjczLjE1NCIsIk1lbWJlcklkIjoxfQ.P3m7RkXJ9TUiUFJ2bbtiyoL7OXaD7ITq_LsWMCRJj04";

    var tokenHandler = new JwtSecurityTokenHandler();
    var securityToken = tokenHandler.ReadToken(jwt);
    var validationParameters = new TokenValidationParameters {IssuerSigningKey = new InMemorySymetricSecurityKey()};
    SecurityToken validated;
    tokenHandler.ValidateToken(jwt, validationParameters, out validated);

    Console.WriteLine(validated.ToString());
}
kentor
  • 16,553
  • 20
  • 86
  • 144

2 Answers2

2

[See update below]

It depends on who signed the JWT token. Typically an authorization server issuing a token publishes metadata with the public key of its signing credential.

Your code can download the metadata and use the public key to validate the token. For example Azure AD publishes its signing keys here.

You could use this code to validate a JWT token issued by Azure AD.

var jwtToken = "<JWT TOKEN>";
var url = "https://login.windows.net/common/federationmetadata/2007-06/federationmetadata.xml";
var serializer = new MetadataSerializer();
MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(url));

var entityDescriptor = (EntityDescriptor)metadata;
var securityTokens = new List<X509SecurityToken>();
var descriptor = entityDescriptor.RoleDescriptors.OfType<SecurityTokenServiceDescriptor>().First();

var x509DataClauses = descriptor.Keys.Where(key => key.KeyInfo != null &&
                                           (key.Use == KeyType.Signing || key.Use == KeyType.Unspecified))
                                     .Select(key => key.KeyInfo.OfType<X509RawDataKeyIdentifierClause>().First());

securityTokens.AddRange(x509DataClauses.Select(token => new X509SecurityToken(new X509Certificate2(token.GetX509RawData()))));

var validationParameters = new TokenValidationParameters
{
    IssuerSigningTokens = securityTokens,
    CertificateValidator = X509CertificateValidator.ChainTrust,
};
SecurityToken validatedToken;
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);

UPDATE: I misread your question and missed you specified the literal value of the symmetric key. You should be able to use a SymmetricSecurityKey like this:

HMACSHA256 hmac = new HMACSHA256(Encoding.ASCII.GetBytes(key));

var validationParameters = new TokenValidationParameters
{
    IssuerSigningKey = new SymmetricSecurityKey(hmac.Key);
};
MvdD
  • 22,082
  • 8
  • 65
  • 93
  • 2
    The JWT token has been issued from a website coded in PHP. When I go to jwt.io I can just paste my JWT and validate it against the key. I have expected something similiarly easy with the C# library. I think your code snippet shows something completely different or? – kentor Mar 23 '17 at 01:16
  • What you get in jwt.io is the decoding of the JWT token. JWT tokens consist of 3 parts that are base64 encoded. If you paste your token into the jwt.io site, it says 'invalid signature' at the bottom because it cannot validate the token without the public key of the signing key. – MvdD Mar 23 '17 at 04:33
  • The JwtSecurityTokenHandler.ValidateToken method tries to validate the authenticity of the token, i.e. whether the signature is valid and therefore the token can be trusted. Trusted means the token was issued by the authorization server you expected (iss claim), intended for your service (aud claim) and within valid time frame (nbf & exp claims). It also guarentees that the token was not modified. – MvdD Mar 23 '17 at 04:38
  • @MvdD, The first sentence of the OP states `the chosen key is 'private'` copy and paste the token then type `private` where you see `secret` and you will see that the token is in fact verified. – Nkosi Mar 23 '17 at 05:44
  • @Nkosi Ha, I completely misread the literal quotes around 'private'. :) Updated the answer. – MvdD Mar 23 '17 at 16:15
0

If you are having the metadata from your identity provider, you can use following code to get the key from metadata and validate your token,

static string _issuer = string.Empty;
static List<X509SecurityToken> _signingTokens = null;    
var stsMetadataAddress = "Your Metadata URL";
static string _audience = "your Audience URL"; //app id
GetTenantInformation(stsMetadataAddress, out issuer, out signingTokens);
Microsoft.IdentityModel.Tokens.SecurityKey key = new 
X509SecurityKey(signingTokens.FirstOrDefault().Certificate);
TokenValidationParameters validationParameters = new TokenValidationParameters
{
   ValidAudience = _audience,
   ValidIssuer = issuer,
   IssuerSigningKey = key
};

Below is the method to get the signing token ans issuer information from metadata,

static void GetTenantInformation(string metadataAddress, out string issuer, out List<X509SecurityToken> signingTokens)
    {
        signingTokens = new List<X509SecurityToken>();

        // The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true. 
        if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
        || string.IsNullOrEmpty(_issuer)
        || _signingTokens == null)
        {
            MetadataSerializer serializer = new MetadataSerializer()
            {
                // turning off certificate validation for demo. Don't use this in production code.
                CertificateValidationMode = X509CertificateValidationMode.None
            };
            MetadataBase metadata = serializer.ReadMetadata(XmlReader.Create(metadataAddress));

            EntityDescriptor entityDescriptor = (EntityDescriptor)metadata;

            // get the issuer name
            if (!string.IsNullOrWhiteSpace(entityDescriptor.EntityId.Id))
            {
                _issuer = entityDescriptor.EntityId.Id;
            }

            // get the signing certs
            _signingTokens = ReadSigningCertsFromMetadata(entityDescriptor);

            _stsMetadataRetrievalTime = DateTime.UtcNow;
        }

        issuer = _issuer;
        signingTokens = _signingTokens;
    }
shivanju0801
  • 131
  • 2
  • 14