/// <summary>
        /// Initializes a new instance of the <see cref="TokenClaimsPrincipal"/> class.
        /// </summary>
        /// <param name="idToken">Token.</param>
        /// <param name="tokenType">Token type.</param>
        public TokenClaimsPrincipal(String accessToken, String idToken, String tokenType, String refreshToken) : base()
        {
            if (String.IsNullOrEmpty(idToken))
            {
                throw new ArgumentNullException(nameof(idToken));
            }
            else if (String.IsNullOrEmpty(tokenType))
            {
                throw new ArgumentNullException(nameof(tokenType));
            }
            else if (tokenType != "urn:ietf:params:oauth:token-type:jwt" &&
                     tokenType != "bearer")
            {
                throw new ArgumentOutOfRangeException(nameof(tokenType), "expected urn:ietf:params:oauth:token-type:jwt");
            }

            // Token
            this.m_idToken     = idToken;
            this.m_accessToken = accessToken;

            String[] tokenObjects = idToken.Split('.');
            // Correct each token to be proper B64 encoding
            for (int i = 0; i < tokenObjects.Length; i++)
            {
                tokenObjects[i] = tokenObjects[i].PadRight(tokenObjects[i].Length + (tokenObjects[i].Length % 4), '=').Replace("===", "=");
            }
            JObject headers = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[0]))),
                    body    = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[1])));


            // Attempt to get the certificate
            if (((String)headers["alg"]).StartsWith("RS"))
            {
                var cert = SecurityUtils.FindCertificate(X509FindType.FindByThumbprint, StoreLocation.CurrentUser, StoreName.My, headers["x5t"].ToString());
                //if (cert == null)
                //	throw new SecurityTokenException(SecurityTokenExceptionType.KeyNotFound, String.Format ("Cannot find certificate {0}", headers ["x5t"]));
                // TODO: Verify signature
            }
            else if (((String)headers["alg"]).StartsWith("HS"))
            {
                // TODO: Verify key
            }

            // Parse the jwt
            List <IClaim> claims = new List <IClaim>();

            foreach (var kf in body)
            {
                String claimName = kf.Key;
                if (!claimMap.TryGetValue(kf.Key, out claimName))
                {
                    claims.AddRange(this.ProcessClaim(kf, kf.Key));
                }
                else
                {
                    claims.AddRange(this.ProcessClaim(kf, claimName));
                }
            }

            IClaim expiryClaim    = claims.Find(o => o.Type == SanteDBClaimTypes.Expiration),
                   notBeforeClaim = claims.Find(o => o.Type == SanteDBClaimTypes.AuthenticationInstant);

            if (expiryClaim == null || notBeforeClaim == null)
            {
                throw new SecurityTokenException("Missing NBF or EXP claim");
            }
            else
            {
                DateTime expiry    = expiryClaim.AsDateTime().ToLocalTime(),
                         notBefore = notBeforeClaim.AsDateTime().ToLocalTime();

                if (expiry == null || expiry < DateTime.Now)
                {
                    throw new SecurityTokenException("Token expired");
                }
                else if (notBefore == null || Math.Abs(notBefore.Subtract(DateTime.Now).TotalMinutes) > 3)
                {
                    throw new SecurityTokenException("Token cannot yet be used (issued in the future)");
                }
            }
            this.RefreshToken = refreshToken;
            this.AddIdentity(new SanteDBClaimsIdentity(body["unique_name"]?.Value <String>().ToLower() ?? body["sub"]?.Value <String>().ToLower(), true, "OAUTH", claims));
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="SanteDB.DisconnectedClient.Security.TokenClaimsPrincipal"/> class.
        /// </summary>
        /// <param name="idToken">Token.</param>
        /// <param name="tokenType">Token type.</param>
        public TokenClaimsPrincipal(String accessToken, String idToken, String tokenType, String refreshToken, OpenIdConfigurationInfo configuration) : base()
        {
            if (String.IsNullOrEmpty(idToken))
            {
                throw new ArgumentNullException(nameof(idToken));
            }
            else if (String.IsNullOrEmpty(tokenType))
            {
                throw new ArgumentNullException(nameof(tokenType));
            }
            else if (
                tokenType != "urn:santedb:session-info" &&
                tokenType != "urn:ietf:params:oauth:token-type:jwt" &&
                tokenType != "bearer")
            {
                throw new ArgumentOutOfRangeException(nameof(tokenType), "expected urn:ietf:params:oauth:token-type:jwt");
            }

            // Token
            this.m_idToken     = idToken;
            this.m_accessToken = accessToken;

            JObject tokenBody = null;

            if (tokenType == "urn:santedb:session-info")
            {
                tokenBody = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(idToken)));
            }
            else
            {
                String[] tokenObjects = idToken.Split('.');
                // Correct each token to be proper B64 encoding
                for (int i = 0; i < tokenObjects.Length; i++)
                {
                    tokenObjects[i] = tokenObjects[i].PadRight(tokenObjects[i].Length + (tokenObjects[i].Length % 4), '=').Replace("===", "=");
                }
                JObject headers = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[0])));
                tokenBody = JObject.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(tokenObjects[1])));

                // Attempt to get the certificate
                if (((String)headers["alg"]).StartsWith("RS"))
                {
                    var cert = X509CertificateUtils.FindCertificate(X509FindType.FindByThumbprint, StoreLocation.CurrentUser, StoreName.My, headers["x5t"].ToString());
                    //if (cert == null)
                    //	throw new SecurityTokenException(SecurityTokenExceptionType.KeyNotFound, String.Format ("Cannot find certificate {0}", headers ["x5t"]));
                    // TODO: Verify signature
                }
                else if (((String)headers["alg"]).StartsWith("HS"))
                {
                    // TODO: Verfiy signature using our client secret
                }
            }

            // Parse the jwt
            List <IClaim> claims = new List <IClaim>();

            foreach (var kf in tokenBody)
            {
                String claimName = kf.Key;
                if (!claimMap.TryGetValue(kf.Key, out claimName))
                {
                    claims.AddRange(this.ProcessClaim(kf, kf.Key));
                }
                else
                {
                    claims.AddRange(this.ProcessClaim(kf, claimName));
                }
            }

            // Validate issuer
            if (configuration != null && !claims.Any(c => c.Type == "iss" && c.Value == configuration?.Issuer))
            {
                throw new SecurityTokenException(SecurityTokenExceptionType.InvalidIssuer, "Issuer mismatch");
            }

            // Validate validity
            IClaim expiryClaim    = claims.Find(o => o.Type == SanteDBClaimTypes.Expiration),
                   notBeforeClaim = claims.Find(o => o.Type == SanteDBClaimTypes.AuthenticationInstant);

            if (expiryClaim == null || notBeforeClaim == null)
            {
                throw new SecurityTokenException(SecurityTokenExceptionType.InvalidClaim, "Missing NBF or EXP claim");
            }
            else
            {
                DateTime expiry    = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
                         notBefore = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                expiry    = expiryClaim.AsDateTime();
                notBefore = notBeforeClaim.AsDateTime();

                if (expiry == null || expiry < DateTime.Now)
                {
                    throw new SecurityTokenException(SecurityTokenExceptionType.TokenExpired, "Token expired");
                }
                else if (notBefore == null || Math.Abs(notBefore.Subtract(DateTime.Now).TotalMinutes) > 3)
                {
                    throw new SecurityTokenException(SecurityTokenExceptionType.NotYetValid, $"Token cannot yet be used (issued {notBefore} but current time is {DateTime.Now})");
                }
            }
            this.RefreshToken = refreshToken;

            this.m_identities.Clear();
            this.m_identities.Add(new SanteDBClaimsIdentity(tokenBody["unique_name"]?.Value <String>() ?? tokenBody["sub"]?.Value <String>(), true, "OAUTH2", claims));
        }