Scott Dellinger
Scott Dellinger

Reputation: 158

What could cause WebApi with HMAC Authentication to only work once?

I have an C# MVC project to which I recently added WebApi. I've secured the API with HMAC Authentication and am using a CustomDelegatingHandler to add the authorization to the header of the request.

I have done this successfully on another project, using the same code, and it works. But on this new project, the Authentication only works once, then every other call to the API fails due to "Unauthorized". This only happens on our development/test server and not on my local machine running the project in Visual Studio 2017.

What might cause this odd behavior?

Here is the CustomDelegatingHandler I'm using:

public class CustomDelegatingHandler : DelegatingHandler
{
    private string APPId = "";
    private string APIKey = "";

    public CustomDelegatingHandler(string appid, string apikey)
    {
        APPId = appid;
        APIKey = apikey;
    }

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {

        HttpResponseMessage response = null;
        string requestContentBase64String = string.Empty;

        string requestUri = System.Web.HttpUtility.UrlEncode(request.RequestUri.AbsoluteUri.ToLower());

        string requestHttpMethod = request.Method.Method;

        //Calculate UNIX time
        DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
        TimeSpan timeSpan = DateTime.UtcNow - epochStart;
        string requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();

        //create random nonce for each request
        string nonce = Guid.NewGuid().ToString("N");

        //Checking if the request contains body, usually will be null wiht HTTP GET and DELETE
        if (request.Content != null)
        {
            byte[] content = await request.Content.ReadAsByteArrayAsync();
            MD5 md5 = MD5.Create();
            //Hashing the request body, any change in request body will result in different hash, we'll incure message integrity
            byte[] requestContentHash = md5.ComputeHash(content);
            requestContentBase64String = Convert.ToBase64String(requestContentHash);
        }

        //Creating the raw signature string
        string signatureRawData = String.Format("{0}{1}{2}{3}{4}{5}", APPId, requestHttpMethod, requestUri, requestTimeStamp, nonce, requestContentBase64String);

        var secretKeyByteArray = Convert.FromBase64String(APIKey);

        byte[] signature = Encoding.UTF8.GetBytes(signatureRawData);

        using (HMACSHA256 hmac = new HMACSHA256(secretKeyByteArray))
        {
            byte[] signatureBytes = hmac.ComputeHash(signature);
            string requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
            //Setting the values in the Authorization header using custom scheme (amx)
            request.Headers.Authorization = new AuthenticationHeaderValue("amx", string.Format("{0}:{1}:{2}:{3}", APPId, requestSignatureBase64String, nonce, requestTimeStamp));
        }

        response = await base.SendAsync(request, cancellationToken);

        return response;
    }
}

Upvotes: 0

Views: 1774

Answers (1)

Dustin Carmon
Dustin Carmon

Reputation: 36

We had a similar issue which turned out to be related to the time stamp of the client vs. the server.

Solved it (server-side) by checking that the request was within the allowed time this way:

(a > b ? a-b : b-a) > maxtime vs a - b > maxtime

If b > a, being an unsigned long (ulong), the value went to a huge value, thus triggering an invalid request condition.

1522896501 - 1522896502 > 300 (18446744073709551615 is what was calculated)

Upvotes: 2

Related Questions