Home > Software design >  How to do DIGEST with POST, PUT and PATCH
How to do DIGEST with POST, PUT and PATCH

Time:03-24

I am at a total loss here. Im currently having success doing a GET request against a web service that implements DIGEST MD5-sess authentication. This works fine. I get the expected result so I figure my 'BUILD DIGEST AUTH HEADER' method works as intended.

I then get the requirement to also support POST, PUT and PATCH but now I run into a whole heap of problems. First request obviously returns a 401 and I read the info in the WWW-Authenticate header and make a MD5-sess auth header taking into account that this is now a POST, PUT or PATCH request.

        var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
        var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));

I keep everything the same but do also obviously add the content to the request on POST, PUT and PATCH. But for some reason I cannot figure out Im locked out of the service. Second request return another 401 even.

Is there something special that needs to be done for DIGEST Auth when method is POST, PUT and PATCH that Im missing?

I have all my requests setup in Postman as well put Postman does not have the same problem. Second call in Postman gets the expected results. A 200 and the expected data on POST, PUT and PATCH.

Im using a slightly modified version DigestAuthFixer.cs that's also available on some other posts here in stackoverflow.

Code below is currently hardcoded to use the MD5-sess method.

public class DigestAuthFixer
{
    private static readonly Random random = new Random(DateTime.Now.Millisecond);
    private readonly AuthData authData;
    readonly UrlResolverStrategyBase urlResolverStrategy;

    public HttpStatusCode StatusCode { get; private set; }

    public DigestAuthFixer(BasicAuth basicAuth, UrlResolverStrategyBase urlResolverStrategy)
    {
        // TODO: Complete member initialization
        authData = new AuthData
        {
            Host = urlResolverStrategy.GetHostName(),
            User = basicAuth.GetUsername(),
            Password = basicAuth.GetPassword(),
        };

        this.urlResolverStrategy = urlResolverStrategy;
    }

    private string CalculateMd5Hash(string input)
    {
        var inputBytes = Encoding.ASCII.GetBytes(input);
        var hash = MD5.Create().ComputeHash(inputBytes);
        var sb = new StringBuilder();
        foreach (var b in hash)
        {
            sb.Append(b.ToString("x2"));
        }
        return sb.ToString();
    }

    private string GrabHeaderVar(string varName, string header)
    {
        var regHeader = new Regex(string.Format(@"{0}=""([^""]*)""", varName));
        var matchHeader = regHeader.Match(header);
        if (matchHeader.Success)
        {
            return matchHeader.Groups[1].Value;
        }
        throw new ApplicationException(string.Format("Header {0} not found", varName));
    }

    private string GetDigestHeader(string dir)
    {
        authData.NC  ;

        string ha1;
        if (authData.Algorithm == "MD5-sess")
        {
            var ha0 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
            ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", ha0, authData.Nonce, authData.Cnonce));
        }
        else
        {
            ha1 = CalculateMd5Hash(string.Format("{0}:{1}:{2}", authData.User, authData.Realm, authData.Password));
        }

        var methodString = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
        var ha2 = CalculateMd5Hash(string.Format("{0}:{1}", methodString, dir));

        var digestResponse = CalculateMd5Hash(string.Format("{0}:{1}:{2:00000000}:{3}:{4}:{5}", ha1, authData.Nonce, authData.NC, authData.Cnonce, authData.Qop, ha2));

        var authString = string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", algorithm=\"{4}\", qop={5}, nc={6:00000000}, cnonce=\"{7}\", response=\"{8}\"", authData.User, authData.Realm, authData.Nonce, dir, authData.Algorithm, authData.Qop, authData.NC, authData.Cnonce, digestResponse);

        return authString;
    }

    public string GrabResponse(string nUrl, string content)
    {   
        var uri = new Uri(authData.Host   nUrl);

        var request = (HttpWebRequest)WebRequest.Create(uri);

        request.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();

        // If we've got a recent Auth header, re-use it!
        if (!string.IsNullOrEmpty(authData.Cnonce) && DateTime.Now.Subtract(authData.CnonceDate).TotalHours < 1.0)
        {
            request.Headers.Add("Authorization", GetDigestHeader(nUrl));
        }

        if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
        {
            request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
        }

        AddContentToBody(request, content);

        HttpWebResponse response = null;
        try
        {
            response = (HttpWebResponse)request.GetResponse();
            StatusCode = response.StatusCode;
        }
        catch (WebException ex)
        {
            // Try to fix a 401 exception by adding a Authorization header
            if (ex.Response == null || ((HttpWebResponse)ex.Response).StatusCode != HttpStatusCode.Unauthorized)
                throw;

            var wwwAuthenticateHeader = ex.Response.Headers["WWW-Authenticate"];

            authData.Realm = GrabHeaderVar("realm", wwwAuthenticateHeader);
            authData.Nonce = GrabHeaderVar("nonce", wwwAuthenticateHeader);
            authData.Qop = GrabHeaderVar("qop", wwwAuthenticateHeader);
            authData.Algorithm = "MD5-sess"; // GrabHeaderVar("algorithm", wwwAuthenticateHeader);

            authData.NC = 0;
            authData.Cnonce = RandomString(8);
            authData.CnonceDate = DateTime.Now;

            string debug = wwwAuthenticateHeader   Environment.NewLine   nUrl   Environment.NewLine   uri.ToString()   Environment.NewLine   authData.ToString();
            var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
            digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();

            AddContentToBody(digestRequest, content);

            var authHeader = GetDigestHeader(nUrl);

            debug  = uri.ToString()   Environment.NewLine;
            debug  = nUrl   Environment.NewLine;
            debug  = authHeader   Environment.NewLine;

            digestRequest.Headers.Add("Authorization", authHeader);

            if (!string.IsNullOrEmpty(urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue))
            {
                request.Headers.Add(HttpHelper.IfMatchHeaderName, urlResolverStrategy.ResolverDataModel.IfMatchHeaderValue);
            }

            HttpWebResponse digestResponse = null;
            try
            {
                //return authHeader;
                digestResponse = (HttpWebResponse)digestRequest.GetResponse();
                StatusCode = digestResponse.StatusCode;
                response = digestResponse;
            }
            catch (Exception digestRequestEx)
            {
                if (digestResponse != null)
                {
                    StatusCode = response.StatusCode;
                }
                else
                {
                    StatusCode = HttpStatusCode.InternalServerError;
                }

                //return "It broke"   Environment.NewLine   debug;
                return "There was a problem with url, username or password ("   digestRequestEx.Message   ")";
            }
        }
        var reader = new StreamReader(response.GetResponseStream());
        return reader.ReadToEnd();
    }

    public string RandomString(int length)
    {
        var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        var stringChars = new char[length];

        for (int i = 0; i < stringChars.Length; i  )
        {
            stringChars[i] = chars[random.Next(chars.Length)];
        }

        return new string(stringChars);
    }

    private void AddContentToBody(HttpWebRequest request, string content)
    {
        if (string.IsNullOrEmpty(content))
            return;

        var data = Encoding.Default.GetBytes(content); // note: choose appropriate encoding

        request.ContentLength = data.Length;
        request.ContentType = HttpHelper.MediaTypes.Json;
        request.Accept = "*/*";
        request.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
        //request.Headers.Add("Accept-Encoding", "gzip, deflate, br");

        using (var streamWriter = new StreamWriter(request.GetRequestStream()))
        {
            streamWriter.Write(content);
        }
    }
}

internal class AuthData
{
    public string Host;
    public string User;
    public string Password;
    public string Realm;
    public string Nonce;
    public string Qop;
    public string Cnonce;
    public DateTime CnonceDate;
    public int NC;
    public string Algorithm;

    public override string ToString()
    {
        string newLine = Environment.NewLine;

        string result = Host   newLine;
        result  = User   newLine;
        result  = Realm   newLine;
        result  = Nonce   newLine;
        result  = Qop   newLine;
        result  = Cnonce   newLine;
        result  = CnonceDate   newLine;
        result  = NC   newLine;
        result  = Algorithm   newLine;

        return result;
    }
}

CodePudding user response:

So apparently it matters in which order you add all the headers to the request. With hookbin I was able to detect that even though I have put an Authorization header on my digestRequest object it did not follow through to hookbin.

My placing the Authorization header addition to just below the setting the method line it all works...

I had no idea that that could pose a problem. The reason my GET method work is because is because 'content' is empty so no headers are added.

var digestRequest = (HttpWebRequest)WebRequest.Create(uri);
digestRequest.Method = urlResolverStrategy.ResolverDataModel.HttpMethod.ToString();
digestRequest.Headers.Add("Authorization", GetDigestHeader(nUrl));
  • Related