126

I've been using the JWT library to decode a Json Web Token, and would like to switch to Microsoft's official JWT implementation, System.IdentityModel.Tokens.Jwt.

The documentation is very sparse, so I'm having a hard time figuring how to accomplish what I've been doing with the JWT library. With the JWT library, there is a Decode method that takes the base64 encoded JWT and turns it into JSON which can then be deserialized. I'd like to do something similar using System.IdentityModel.Tokens.Jwt, but after a fair amount of digging, cannot figure out how.

For what it's worth, I'm reading the JWT token from a cookie, for use with Google's identity framework.

Any help would be appreciated.

w.brian
  • 16,296
  • 14
  • 69
  • 118
  • 1
    https://stackoverflow.com/questions/52928670/jwt-role-authentication-in-controller-asp-net-core-2-1 ? – Roman Pokrovskij Nov 12 '18 at 10:58
  • Here's a hands-on answer on how to fetch google certificates and to verify the token - https://stackoverflow.com/questions/29757140/validating-google-openid-connect-jwt-id-token – rothschild86 Aug 13 '20 at 07:40

3 Answers3

179

Within the package there is a class called JwtSecurityTokenHandler which derives from System.IdentityModel.Tokens.SecurityTokenHandler. In WIF this is the core class for deserialising and serialising security tokens.

The class has a ReadToken(String) method that will take your base64 encoded JWT string and returns a SecurityToken which represents the JWT.

The SecurityTokenHandler also has a ValidateToken(SecurityToken) method which takes your SecurityToken and creates a ReadOnlyCollection<ClaimsIdentity>. Usually for JWT, this will contain a single ClaimsIdentity object that has a set of claims representing the properties of the original JWT.

JwtSecurityTokenHandler defines some additional overloads for ValidateToken, in particular, it has a ClaimsPrincipal ValidateToken(JwtSecurityToken, TokenValidationParameters) overload. The TokenValidationParameters argument allows you to specify the token signing certificate (as a list of X509SecurityTokens). It also has an overload that takes the JWT as a string rather than a SecurityToken.

The code to do this is rather complicated, but can be found in the Global.asax.cx code (TokenValidationHandler class) in the developer sample called "ADAL - Native App to REST service - Authentication with ACS via Browser Dialog", located at

http://code.msdn.microsoft.com/AAL-Native-App-to-REST-de57f2cc

Alternatively, the JwtSecurityToken class has additional methods that are not on the base SecurityToken class, such as a Claims property that gets the contained claims without going via the ClaimsIdentity collection. It also has a Payload property that returns a JwtPayload object that lets you get at the raw JSON of the token. It depends on your scenario which approach it most appropriate.

The general (i.e. non JWT specific) documentation for the SecurityTokenHandler class is at

http://msdn.microsoft.com/en-us/library/system.identitymodel.tokens.securitytokenhandler.aspx

Depending on your application, you can configure the JWT handler into the WIF pipeline exactly like any other handler.

There are 3 samples of it in use in different types of application at

http://code.msdn.microsoft.com/site/search?f%5B0%5D.Type=SearchText&f%5B0%5D.Value=aal&f%5B1%5D.Type=User&f%5B1%5D.Value=Azure%20AD%20Developer%20Experience%20Team&f%5B1%5D.Text=Azure%20AD%20Developer%20Experience%20Team

Probably, one will suite your needs or at least be adaptable to them.

Mike Goodwin
  • 8,810
  • 2
  • 35
  • 50
  • 3
    I really appreciate your answer. So, once I have the ClaimsIdentity, how do I verify it against a public key? Specifically, I'm trying to verify a google identity toolkit JWT against their public key (https://www.gstatic.com/authtoolkit/cert/gitkit_cert.pem) – w.brian Sep 20 '13 at 14:07
  • 4
    Updated my answer - I couldn't fit the full source for this in, but I pointed you in the direction of the appropriate developer sample. Hope it helps. – Mike Goodwin Sep 20 '13 at 15:59
  • 4
    @w.brian - I'm trying to do the same. I have a token which I can decode, and a public key which I want to verify, but even looking at these samples I'm struggling to see how I do this. Do you have any pointers to which code actually helped you? Thanks. – Barguast Mar 09 '16 at 14:53
  • ValidateToken suffers from the 2038 problem – Patrick Oct 08 '21 at 17:06
  • Thanks for your detailed answer. Where you say: "The code to do this is rather complicated, but can be found in the Global.asax.cx code (TokenValidationHandler class) in the developer sample called "ADAL - Native App to REST service - Authentication with ACS via Browser Dialog", located at http://code.msdn.microsoft.com/AAL-Native-App-to-REST-de57f2cc" Looks like the project is not available anymore? Would you know where to locate this TokenValidationHandler class ? – cmarrades May 09 '22 at 09:04
32

I am just wondering why to use some libraries for JWT token decoding and verification at all.

Encoded JWT token can be created using following pseudocode

var headers = base64URLencode(myHeaders);
var claims = base64URLencode(myClaims);
var payload = header + "." + claims;

var signature = base64URLencode(HMACSHA256(payload, secret));

var encodedJWT = payload + "." + signature;

It is very easy to do without any specific library. Using following code:

using System;
using System.Text;
using System.Security.Cryptography;

public class Program
{   
    // More info: https://stormpath.com/blog/jwt-the-right-way/
    public static void Main()
    {           
        var header = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
        var claims = "{\"sub\":\"1047986\",\"email\":\"jon.doe@eexample.com\",\"given_name\":\"John\",\"family_name\":\"Doe\",\"primarysid\":\"b521a2af99bfdc65e04010ac1d046ff5\",\"iss\":\"http://example.com\",\"aud\":\"myapp\",\"exp\":1460555281,\"nbf\":1457963281}";

        var b64header = Convert.ToBase64String(Encoding.UTF8.GetBytes(header))
            .Replace('+', '-')
            .Replace('/', '_')
            .Replace("=", "");
        var b64claims = Convert.ToBase64String(Encoding.UTF8.GetBytes(claims))
            .Replace('+', '-')
            .Replace('/', '_')
            .Replace("=", "");

        var payload = b64header + "." + b64claims;
        Console.WriteLine("JWT without sig:    " + payload);

        byte[] key = Convert.FromBase64String("mPorwQB8kMDNQeeYO35KOrMMFn6rFVmbIohBphJPnp4=");
        byte[] message = Encoding.UTF8.GetBytes(payload);

        string sig = Convert.ToBase64String(HashHMAC(key, message))
            .Replace('+', '-')
            .Replace('/', '_')
            .Replace("=", "");

        Console.WriteLine("JWT with signature: " + payload + "." + sig);        
    }

    private static byte[] HashHMAC(byte[] key, byte[] message)
    {
        var hash = new HMACSHA256(key);
        return hash.ComputeHash(message);
    }
}

The token decoding is reversed version of the code above.To verify the signature you will need to the same and compare signature part with calculated signature.

UPDATE: For those how are struggling how to do base64 urlsafe encoding/decoding please see another SO question, and also wiki and RFCs

Regfor
  • 8,515
  • 1
  • 38
  • 51
  • 2
    Nice answer. Although since you show HMAC based signing here, it may make sense to be aware of some critical vulnerabilities in libraries that implement HMAC verification as detailed on Auth0 site here: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/ – Sudhanshu Mishra Jun 27 '16 at 06:45
  • 2
    I feel this is the best answer. The OP requested info on JWT specifically which this article addresses with a clear example.. – webworm Jan 16 '17 at 15:15
  • 23
    This answer explains and demonstrates how to **en**code a JWT when the question is quite clearly about **de**coding. This may be a nice answer but it is an answer *to an entirely different question*. – Deltics Jan 25 '17 at 07:41
  • 2
    @Deltics I think that even computer science degree is not needed to rewrite encoding algorithm to decode token. If you understand how to encode - you understand how to decode – Regfor Jan 26 '17 at 08:57
  • 45
    The idea of an "answer" is to address a question, not pose a puzzle by expecting someone to solve some sort of reverse-intention puzzle. Bedsides, knowing how to encode does *not* necessarily mean that you also then know how to decode since this may also involve dealing with 3rd party tokens and retrieving keys to verify their signatures, as opposed to simply using a key to sign your own. In any event, an answer that does *not* actually answer the question by definition is **not** the "*better*" answer when compared to one that *does*, which is the observation to which I was responding. – Deltics Jan 26 '17 at 19:53
  • 2
    @Deltics In this case (JWT, opaque token) decoding can be very easily done following encoding example. For signature check you will need do same as in example. Let reversing it will be homework for people reading it. I think that such an easy task does not require any library. So it is an alternative answer. Looking at up rating and and at your down vote I think this answer is useful. Anyway you are welcome to post better answer or use first one, reverse code and post here or even complain to admins. – Regfor Jan 27 '17 at 11:46
  • For me, this is the best answer! – Hashim Akhtar Nov 13 '17 at 11:51
  • Question for you fine gentlemen, how would you reverse .Replace("=", ""); ?? @regfor and others, I am genuinely asking do you just put and = after every letter or is it insignificant ? – Shereef Marzouk Jan 23 '18 at 22:45
  • @ShereefMarzouk Please read standards https://tools.ietf.org/html/rfc4648#section-4. '=' is used for padding in base64. Please read also wikipedia https://en.wikipedia.org/wiki/Base64 After reading how base64 works, you can read decode with and without padding sections. How to reverse? Add the padding to the end of encoded string if byte length (L mod 4) equals 1 or 2, see here https://stackoverflow.com/questions/26353710/how-to-achieve-base64-url-safe-encoding-in-c – Regfor Jan 24 '18 at 17:46
  • 1
    Today I was decrypting jwt token and another answer here on SOF told me about the padding cha and the mod 4, thanks for your answer @Regfor in that case this is easily reversible just read it bottom to top reversing the .replace params skipping the = one and then pad at the end, so simple ;) – Shereef Marzouk Jan 25 '18 at 03:05
  • This is a great answer (simple example) to explaining how it works. – vito Aug 15 '18 at 09:10
  • You do not add a signature to the JWT. What you are doing is to compute a hash value of the payload and adding that instead of a signature. The hash value should be created without the key and then signed by the key. Example using BouncyCastle.Crypto can be found here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/730bfdeb-f0d0-47e5-9e52-7352372232a2/c-rs256-sign-jwt-signature-using-private-key-from-json-file – huha Feb 15 '19 at 06:39
  • 1
    @huha example you provide uses RS256 means RSA encryption, JWT supports both HS256 and RS256, in example there is HS256 which means HMAC SHA 256. Most of the uses of JWT are HS256 despite RS256 is better – Regfor Feb 15 '19 at 08:15
  • 9
    Do not code security relevant parts yourself unless you really know what you are doing. There is always something you could easily do wrong. The sheer fact that vulnerable jwt libraries exist proof that it is a bad idea to code this yourself unless you really think you know JWT better than the authors of these libraries. However, this answer is still useful to gain JWT understanding. – Christopher K. May 08 '20 at 12:39
4

I had version issues between System.IdentityModel.Tokens and System.IdentityModel.Tokens.Jwt, which is a known issue after version 5.0.0.0 of Jwt. So, instead I downloaded the latest release of Microsoft.IdentityModel.Tokens - note Microsoft - and all worked fine. Here is a nice snippet I made for validating and decoding a custom-generated JWT token and parsing its JSON content.

using System.Collections.Generic;
using System.Linq;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

public static void Main()
{
    var key = "qwertyuiopasdfghjklzxcvbnm123456";
    var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(key));

    string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2NDA0MDY1MjIsImV4cCI6MTY3MTk0MjUyMiwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsImZvbyI6ImJhciJ9.QqcxZWEUt5YLraLRg5550Ls7aMVqm7aCUcbU7uB1qgY";

    TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
    {
        IssuerSigningKey = securityKey,
        RequireExpirationTime = true,
        ValidateLifetime = true,
        ValidateAudience = true,
        ValidateIssuer = true,
        ValidIssuer = "Online JWT Builder",
        ValidAudience = "www.example.com"
    };

    if (ValidateToken(token, tokenValidationParameters))
    {
        var TokenInfo = new Dictionary<string, string>();
        var handler = new JwtSecurityTokenHandler();
        var jwtSecurityToken = handler.ReadJwtToken(token);
        var claims = jwtSecurityToken.Claims.ToList();

        foreach (var claim in claims)
        {
            TokenInfo.Add(claim.Type, claim.Value);
        }

        string sub = jwtSecurityToken.Subject;
        string iss = jwtSecurityToken.Issuer;
        DateTime iat = jwtSecurityToken.IssuedAt;
        List<string> audiences = new List<string>(jwtSecurityToken.Audiences);
        DateTime exp = jwtSecurityToken.ValidTo;
        string bar;
        bool ifBar = TokenInfo.TryGetValue("foo", out bar);
        Console.WriteLine("Subject: " + sub);
        Console.WriteLine("Issuer: " + iss);
        Console.WriteLine("Issued At: " + iat);
        foreach (var member in audiences)
        {
            Console.WriteLine("Audience: " + member);
        }
        Console.WriteLine("Expiration: " + exp);
        Console.WriteLine("foo: " + bar);
    }
    Console.ReadLine();
}

private static bool ValidateToken(string token, TokenValidationParameters tvp)
{
    try
    {
        var handler = new JwtSecurityTokenHandler();
        SecurityToken securityToken;
        ClaimsPrincipal principal = handler.ValidateToken(token, tvp, out securityToken);
        return true;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return false;
    }
}

Output

Subject: jrocket@example.com
Issuer: Online JWT Builder
Issued At: 12/25/2022 4:28:42 AM
Audience: www.example.com
Expiration: 12/25/2022 4:28:42 AM
foo: bar
Max Voisard
  • 1,685
  • 1
  • 8
  • 18