質問

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.

役に立ちましたか?

解決

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.

他のヒント

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

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
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top