Is there any JSON Web Token (JWT) example in C#?

前端 未结 9 2238
悲哀的现实
悲哀的现实 2020-11-28 01:10

I feel like I\'m taking crazy pills here. Usually there\'s always a million library and samples floating around the web for any given task. I\'m trying to implement authenti

相关标签:
9条回答
  • 2020-11-28 01:38

    Here is a working example:

    http://zavitax.wordpress.com/2012/12/17/logging-in-with-google-service-account-in-c-jwt/

    It took quite some time to collect the pieces scattered over the web, the docs are rather incomplete...

    0 讨论(0)
  • 2020-11-28 01:41

    Thanks everyone. I found a base implementation of a Json Web Token and expanded on it with the Google flavor. I still haven't gotten it completely worked out but it's 97% there. This project lost it's steam, so hopefully this will help someone else get a good head-start:

    Note: Changes I made to the base implementation (Can't remember where I found it,) are:

    1. Changed HS256 -> RS256
    2. Swapped the JWT and alg order in the header. Not sure who got it wrong, Google or the spec, but google takes it the way It is below according to their docs.
    public enum JwtHashAlgorithm
    {
        RS256,
        HS384,
        HS512
    }
    
    public class JsonWebToken
    {
        private static Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>> HashAlgorithms;
    
        static JsonWebToken()
        {
            HashAlgorithms = new Dictionary<JwtHashAlgorithm, Func<byte[], byte[], byte[]>>
                {
                    { JwtHashAlgorithm.RS256, (key, value) => { using (var sha = new HMACSHA256(key)) { return sha.ComputeHash(value); } } },
                    { JwtHashAlgorithm.HS384, (key, value) => { using (var sha = new HMACSHA384(key)) { return sha.ComputeHash(value); } } },
                    { JwtHashAlgorithm.HS512, (key, value) => { using (var sha = new HMACSHA512(key)) { return sha.ComputeHash(value); } } }
                };
        }
    
        public static string Encode(object payload, string key, JwtHashAlgorithm algorithm)
        {
            return Encode(payload, Encoding.UTF8.GetBytes(key), algorithm);
        }
    
        public static string Encode(object payload, byte[] keyBytes, JwtHashAlgorithm algorithm)
        {
            var segments = new List<string>();
            var header = new { alg = algorithm.ToString(), typ = "JWT" };
    
            byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
            byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
            //byte[] payloadBytes = Encoding.UTF8.GetBytes(@"{"iss":"761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/prediction","aud":"https://accounts.google.com/o/oauth2/token","exp":1328554385,"iat":1328550785}");
    
            segments.Add(Base64UrlEncode(headerBytes));
            segments.Add(Base64UrlEncode(payloadBytes));
    
            var stringToSign = string.Join(".", segments.ToArray());
    
            var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
    
            byte[] signature = HashAlgorithms[algorithm](keyBytes, bytesToSign);
            segments.Add(Base64UrlEncode(signature));
    
            return string.Join(".", segments.ToArray());
        }
    
        public static string Decode(string token, string key)
        {
            return Decode(token, key, true);
        }
    
        public static string Decode(string token, string key, bool verify)
        {
            var parts = token.Split('.');
            var header = parts[0];
            var payload = parts[1];
            byte[] crypto = Base64UrlDecode(parts[2]);
    
            var headerJson = Encoding.UTF8.GetString(Base64UrlDecode(header));
            var headerData = JObject.Parse(headerJson);
            var payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
            var payloadData = JObject.Parse(payloadJson);
    
            if (verify)
            {
                var bytesToSign = Encoding.UTF8.GetBytes(string.Concat(header, ".", payload));
                var keyBytes = Encoding.UTF8.GetBytes(key);
                var algorithm = (string)headerData["alg"];
    
                var signature = HashAlgorithms[GetHashAlgorithm(algorithm)](keyBytes, bytesToSign);
                var decodedCrypto = Convert.ToBase64String(crypto);
                var decodedSignature = Convert.ToBase64String(signature);
    
                if (decodedCrypto != decodedSignature)
                {
                    throw new ApplicationException(string.Format("Invalid signature. Expected {0} got {1}", decodedCrypto, decodedSignature));
                }
            }
    
            return payloadData.ToString();
        }
    
        private static JwtHashAlgorithm GetHashAlgorithm(string algorithm)
        {
            switch (algorithm)
            {
                case "RS256": return JwtHashAlgorithm.RS256;
                case "HS384": return JwtHashAlgorithm.HS384;
                case "HS512": return JwtHashAlgorithm.HS512;
                default: throw new InvalidOperationException("Algorithm not supported.");
            }
        }
    
        // from JWT spec
        private static string Base64UrlEncode(byte[] input)
        {
            var output = Convert.ToBase64String(input);
            output = output.Split('=')[0]; // Remove any trailing '='s
            output = output.Replace('+', '-'); // 62nd char of encoding
            output = output.Replace('/', '_'); // 63rd char of encoding
            return output;
        }
    
        // from JWT spec
        private static byte[] Base64UrlDecode(string input)
        {
            var output = input;
            output = output.Replace('-', '+'); // 62nd char of encoding
            output = output.Replace('_', '/'); // 63rd char of encoding
            switch (output.Length % 4) // Pad with trailing '='s
            {
                case 0: break; // No pad chars in this case
                case 2: output += "=="; break; // Two pad chars
                case 3: output += "="; break; // One pad char
                default: throw new System.Exception("Illegal base64url string!");
            }
            var converted = Convert.FromBase64String(output); // Standard base64 decoder
            return converted;
        }
    }
    

    And then my google specific JWT class:

    public class GoogleJsonWebToken
    {
        public static string Encode(string email, string certificateFilePath)
        {
            var utc0 = new DateTime(1970,1,1,0,0,0,0, DateTimeKind.Utc);
            var issueTime = DateTime.Now;
    
            var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
            var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds; // Expiration time is up to 1 hour, but lets play on safe side
    
            var payload = new
            {
                iss = email,
                scope = "https://www.googleapis.com/auth/gan.readonly",
                aud = "https://accounts.google.com/o/oauth2/token",
                exp = exp,
                iat = iat
            };
    
            var certificate = new X509Certificate2(certificateFilePath, "notasecret");
    
            var privateKey = certificate.Export(X509ContentType.Cert);
    
            return JsonWebToken.Encode(payload, privateKey, JwtHashAlgorithm.RS256);
        }
    }
    
    0 讨论(0)
  • 2020-11-28 01:44

    Take a look at Google Client Library for .NET.

    0 讨论(0)
  • 2020-11-28 01:46

    This is my implementation of (Google) JWT Validation in .NET. It is based on other implementations on Stack Overflow and GitHub gists.

    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Net.Http;
    using System.Security.Claims;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace QuapiNet.Service
    {
        public class JwtTokenValidation
        {
            public async Task<Dictionary<string, X509Certificate2>> FetchGoogleCertificates()
            {
                using (var http = new HttpClient())
                {
                    var response = await http.GetAsync("https://www.googleapis.com/oauth2/v1/certs");
    
                    var dictionary = await response.Content.ReadAsAsync<Dictionary<string, string>>();
                    return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value)));
                }
            }
    
            private string CLIENT_ID = "xxx.apps.googleusercontent.com";
    
            public async Task<ClaimsPrincipal> ValidateToken(string idToken)
            {
                var certificates = await this.FetchGoogleCertificates();
    
                TokenValidationParameters tvp = new TokenValidationParameters()
                {
                    ValidateActor = false, // check the profile ID
    
                    ValidateAudience = true, // check the client ID
                    ValidAudience = CLIENT_ID,
    
                    ValidateIssuer = true, // check token came from Google
                    ValidIssuers = new List<string> { "accounts.google.com", "https://accounts.google.com" },
    
                    ValidateIssuerSigningKey = true,
                    RequireSignedTokens = true,
                    IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)),
                    IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
                    {
                        return certificates
                        .Where(x => x.Key.ToUpper() == kid.ToUpper())
                        .Select(x => new X509SecurityKey(x.Value));
                    },
                    ValidateLifetime = true,
                    RequireExpirationTime = true,
                    ClockSkew = TimeSpan.FromHours(13)
                };
    
                JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
                SecurityToken validatedToken;
                ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);
    
                return cp;
            }
        }
    }
    

    Note that, in order to use it, you need to add a reference to the NuGet package System.Net.Http.Formatting.Extension. Without this, the compiler will not recognize the ReadAsAsync<> method.

    0 讨论(0)
  • 2020-11-28 01:47

    After all these months have passed after the original question, it's now worth pointing out that Microsoft has devised a solution of their own. See http://blogs.msdn.com/b/vbertocci/archive/2012/11/20/introducing-the-developer-preview-of-the-json-web-token-handler-for-the-microsoft-net-framework-4-5.aspx for details.

    0 讨论(0)
  • 2020-11-28 01:55

    I've never used it but there is a JWT implementation on NuGet.

    Package: https://nuget.org/packages/JWT

    Source: https://github.com/johnsheehan/jwt

    .NET 4.0 compatible: https://www.nuget.org/packages/jose-jwt/

    You can also go here: https://jwt.io/ and click "libraries".

    0 讨论(0)
提交回复
热议问题