/// <summary>
        /// Sends an HTTP request to the inner handler to send to the server as an asynchronous operation.
        /// </summary>
        /// <param name="request">The HTTP request message to send to the server.</param>
        /// <param name="cancellationToken">A cancellation token to cancel operation.</param>
        /// <returns>
        /// Returns <see cref="T:System.Threading.Tasks.Task`1" />. The task object representing the asynchronous operation.
        /// </returns>
        protected override async Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            if (request.Headers.Date == null)
            {
                request.Headers.Date = DateTime.UtcNow;
            }

            // Check if we have request content, if we do then we need to add a Content-MD5 header if it doesn't already exist.
            // However we can't do this if we're chunked.
            if (request.Headers.TransferEncodingChunked == null || !(bool)request.Headers.TransferEncodingChunked)
            {
                // We can't rely on the length header, as it's not set yet.
                if (request.Content != null && request.Content.Headers.ContentMD5 == null)
                {
                    byte[] contentHash = SignatureValidator.CalculateBodyMd5(request).Result;
                    if (contentHash != null)
                    {
                        request.Content.Headers.ContentMD5 = contentHash;
                    }
                }
            }

            byte[] hash = SharedKeySignature.Calculate(request, this.accountName, this.sharedSecret);
            request.Headers.Authorization = new AuthenticationHeaderValue(
                SharedKeyAuthentication.Scheme,
                string.Format(CultureInfo.InvariantCulture, "{0}:{1}", this.accountName, Convert.ToBase64String(hash)));

            // And finally hand off the request to the InnerHandler.
            return(await base.SendAsync(request, cancellationToken));
        }
        /// <summary>
        /// Validates the specified request.
        /// </summary>
        /// <param name="request">The <see cref="HttpRequestMessage"/> to validate</param>
        /// <param name="resolver">An account name to shared secret resolver.</param>
        /// <param name="maxAge">The maximum age of a message that will be accepted</param>
        /// <returns>A ClaimsPrincipal for the the identity which owns the message.</returns>
        public static ClaimsPrincipal Validate(HttpRequestMessage request, Func <string, byte[]> resolver, TimeSpan maxAge)
        {
            AuthenticationHeaderValue authenticationHeader = null;

            if (request.Headers != null)
            {
                authenticationHeader = request.Headers.Authorization;
            }

            string accountName;

            byte[] sentHmac;

            if (request.Headers != null && DateTime.Now - request.Headers.Date > maxAge)
            {
                throw new ForbiddenException("Request expired.");
            }

            if (authenticationHeader == null ||
                string.IsNullOrWhiteSpace(authenticationHeader.Parameter) ||
                authenticationHeader.Scheme != SharedKeyAuthentication.Scheme ||
                !SharedKeyAuthentication.TryParse(authenticationHeader.Parameter, out accountName, out sentHmac))
            {
                return(AnonymousPrincipal);
            }

            byte[] sharedKey = resolver(accountName);

            if (sharedKey == null || sharedKey.Length == 0)
            {
                // Account not found
                throw new UnauthorizedException();
            }

            if (request.Headers.TransferEncodingChunked == null || !(bool)request.Headers.TransferEncodingChunked)
            {
                if (request.Content.Headers.ContentLength != null && (long)request.Content.Headers.ContentLength > 0)
                {
                    var sentHash = request.Content.Headers.ContentMD5;

                    if (sentHash == null)
                    {
                        throw new ForbiddenException(
                                  "Content-MD5 header must be specified when a request body is included.");
                    }

                    if (!CompareHash(sentHash, SignatureValidator.CalculateBodyMd5(request).Result))
                    {
                        throw new PreconditionFailedException("Content-MD5 does not match the request body.");
                    }
                }
            }

            // Now check the actual auth signature.
            byte[] calculatedHmac = SharedKeySignature.Calculate(request, accountName, sharedKey);

            if (!CompareHash(sentHmac, calculatedHmac))
            {
                throw new ForbiddenException("Token validation failed.");
            }

            var claims = new List <Claim> {
                new Claim(ClaimTypes.Name, accountName)
            };
            var claimsIdentity  = new ClaimsIdentity(claims, SharedKeyAuthentication.Scheme);
            var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

            return(claimsPrincipal);
        }