3
votes

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());
}
2
I have solved my problem by using jose-jwt which was super easy.kentor

2 Answers

2
votes

[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);
};
0
votes

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;
    }