0
votes

I'm trying to create and read (validate) a JSON Web Token (JWT) in an Azure Function using C#. I came across this post:

https://www.codeproject.com/Tips/1208535/Create-And-Consume-JWT-Tokens-in-csharp

which outlines the process very nicely. Being relatively new to Azure Functions, I put the reference to "System.IdentityModel.Tokens.Jwt" in my project.json file like this:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "System.IdentityModel.Tokens.Jwt" : "5.0"
      }
    }
   }
}

The version I used came from this post: Namespaces for .NET JWT token validation: System vs. Microsoft, which talks about versioning issues back in 2016.

Unfortunately, this didn't work. References to SecurityAlgorithms, JwtHeader, JwtPayload, JwtSecurityToken, and JwtSecurityTokenHandler all report, "[run.csx] The type or namespace name 'class name' could not be found (are you missing a using directive or an assembly reference?)".

Researching further, I discovered this page: https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt/, which displays Nuget version information for System.IdentityModel.Tokens.Jwt. After trying several versions (by changing the version in my project.json file), I've still had no luck in getting the Function App to recognize the classes I need.

I assume that this is a versioning issue. If so, where can I go to determine which version of "System.IdentityModel.Tokens.Jwt" is compatible with "net46"? I haven't written C# code in years (I'm a Java developer), so I may be wrong about the versioning assumption.

BTW, here's what the code in my function looks like, it's appears exactly like the code sample in: https://www.codeproject.com/Tips/1208535/Create-And-Consume-JWT-Tokens-in-csharp. The only difference is I've wrapped it in a Function App.

using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.IdentityModel;  
using System.Security; 
using System.Text;
using System.IdentityModel.Tokens;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    // Define const Key this should be private secret key  stored in some safe place
           string key = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";

           // Create Security key  using private key above:
           // not that latest version of JWT using Microsoft namespace instead of System
           var securityKey = new Microsoft
               .IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));

           // Also note that securityKey length should be >256b
           // so you have to make sure that your private key has a proper length
           //
           var credentials = new Microsoft.IdentityModel.Tokens.SigningCredentials
                             (securityKey, SecurityAlgorithms.HmacSha256Signature);

           //  Finally create a Token
           var header = new JwtHeader(credentials);

           //Some PayLoad that contain information about the  customer
           var payload = new JwtPayload
           {
               { "some ", "hello "},
               { "scope", "http://dummy.com/"},
           };


           //
           var secToken = new JwtSecurityToken(header, payload);
           var handler = new JwtSecurityTokenHandler();

           // Token to String so you can use it in your client
           var tokenString = handler.WriteToken(secToken);

           // And finally when  you received token from client
           // you can  either validate it or try to  read
           var token = handler.ReadJwtToken(tokenString);

    return req.CreateResponse(HttpStatusCode.Created, "test");
}

So, my questions are:

  1. Which version of System.IdentityModel.Tokens should I use with "net46" in my project file?
  2. The next time this happens, how do I determine myself which versions work together?
2
What if you remove DocumentDB.Core from your package list? I wonder if you're getting a conflict so things aren't installing correctly. - brettsam
Thanks brettsam. I did remove it after I made this post. I also removed the references to Console. The changes didn't make a difference. - rbivens

2 Answers

1
votes

I just tried this and saw the same thing. You're missing a reference to System.IdentityModel and a using System.IdentityModel.Tokens.Jwt;

Changing to this got things building:

#r "System.IdentityModel"

using System.Net;
using System.IdentityModel;
using System.Security;
using System.Text;
using System.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

I'd also recommend you move your JWT package reference up to 5.2.4, which is the latest version of that package.

0
votes

I figured it out. From various sites and hundreds of combinations of versions, it works. I wish I could explain why, but instead I'll post the working code here with the appropriate libraries listed. If anyone else runs into this problem, I hope it helps. Thanks for looking into this brettsam!

The Function App looks like this:

using System;
using System.Net; 
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using System.Configuration;


public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string token = JwtManager.GenerateToken("[email protected]", 60);
    ClaimsPrincipal simplePrinciple = JwtManager.GetPrincipal(token);

    var identity = simplePrinciple.Identity as ClaimsIdentity;  
    log.Info(identity.IsAuthenticated.ToString());

    var usernameClaim = identity.FindFirst(ClaimTypes.Name);  
    var username = usernameClaim ? .Value;
    log.Info(username);

    return req.CreateResponse(HttpStatusCode.Created, token);
}


public static class JwtManager
{
    private static string secret = ConfigurationManager.AppSettings["FunctionsJwtSecret"];

    public static string GenerateToken(string username, int expireMinutes = 60)
    {
        var symmetricKey = Convert.FromBase64String(secret);
        var tokenHandler = new JwtSecurityTokenHandler();
        var now = DateTime.UtcNow;

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, username)
            }),

            Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256Signature)
        };

        var stoken = tokenHandler.CreateToken(tokenDescriptor);
        var token = tokenHandler.WriteToken(stoken);
        return token;
    }

    public static ClaimsPrincipal GetPrincipal(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;

            if (jwtToken == null)
                return null;

            var symmetricKey = Convert.FromBase64String(secret);

            var validationParameters = new TokenValidationParameters()
            {
                RequireExpirationTime = true,
                ValidateIssuer = false,
                ValidateAudience = false,
                IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
            };

            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
            // log.Info(securityToken.ToString());

            return principal;
        }

        catch (Exception)
        {
            return null;
        }
    }
}

And the project.json looks like this:

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.IdentityModel.Logging" : "1.0.0.127",
        "Microsoft.IdentityModel.Tokens" : "5.0.0.127",
        "Newtonsoft.Json" : "9.0.0.0",
        "System.IdentityModel.Tokens.Jwt" : "5.0.0.127"
      }
    }
   }
}

Again, I don't know why this combination of versions work together, but I hope this saves someone else 20 hours of tedious trial and error.