[RESOLVED]: Digest Authentication with Synapse lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- jamhitz · 2012-01-10 21:36 Hello. I've been trying to use Synapse to access a secure web resource that uses Digest Authentication. The Username and Password properties of THttpSend don't seem to work. Does Digest Authentication in Synapse work out of the box or do I have to manually negotiate my credentials as indicated here http://en.wikipedia.org/wiki/Digest_access_authentication? Thanks ---------------------------------------------------------------------- ludob · 2012-01-11 07:03 Only basic authorization is supported. When you construct the digest authorization header yourself, make sure THTTPSend.User is left empty. ---------------------------------------------------------------------- jamhitz · 2012-01-11 23:16 Finally!! I finally managed to get Digest Authentication working, like you can see below. I know that in my code below I make a few assumptions (I am assuming qop=auth at all times), but most of the good stuff is in there. Code: [Select]uses ..., HttpSend, LSHTTPSend,  md5, DateUtils, LHTTPUtil; var      intNC : Integer;      oldNonce, oldCNonce : String;      function StripQuotes(strIn:String):String;      procedure MozillaHeaders(var aHttpSend: THttpSend); procedure TForm1.btnGetClick(Sender: TObject); var ht: THttpSend;     aList, aCookies : TStringList;     x,y: Integer;     isOk: Boolean;     h1, h2, h3, uName, aPwd, aURI, aHost:String;     realm, nonce, qop, nc, cnonce, response, opaque, aStr, aMethod:String;     aPort:Word; begin     uName := 'admin'; aPwd := 'p@ssw0rd'; aMethod := 'GET';     DecomposeURL(edtUrl.Text,aHost,aUri,aPort);     ht := ThttpSend.Create;     aList := TStringList.Create;     aCookies := TStringList.Create;     try         MozillaHeaders(ht);         isOk := ht.HTTPMethod(aMethod,edtUrl.Text);         if (ht.ResultCode = 401) and (UpperCase(ht.ResultString)='UNAUTHORIZED') then         begin             aCookies.Text := ht.Cookies.Text;             for x:= 0 to Pred(ht.Headers.Count) do             begin                 if LeftStr(UpperCase(ht.Headers.Strings[x]), 24) = 'WWW-AUTHENTICATE: DIGEST' then                 begin                     aList.Clear;                     aList.StrictDelimiter := true;                     aList.Delimiter := ',';                     aList.DelimitedText :=  trim(Copy(ht.Headers.Strings[x], 25, 400));                     for y := 0 to pred(aList.Count) do                     begin                         aStr := trim(aList.Strings[y]);                         if LeftStr(aStr,5) = 'realm' then realm := StripQuotes(MidStr(aStr,7,Length(aStr)));                         if LeftStr(aStr,5) = 'nonce' then nonce := StripQuotes(MidStr(aStr,7,Length(aStr)));                         if LeftStr(aStr,3) = 'qop' then qop := StripQuotes(MidStr(aStr,5,Length(aStr)));                         if LeftStr(aStr,6) = 'opaque' then opaque := StripQuotes(MidStr(aStr,8,Length(aStr)));                     end;                     isOk := false;                     if OldNonce = Nonce then                     begin                         inc(intNC);                         cnonce := oldNonce;                     end                     else                     begin                         intNc := 1;                         cnonce := md5Print(md5String(IntToStr(DateTimeToUnix(Now()))));                     end;                     nc := RightStr('00000000' + IntToStr(intNc), 8);                     h1 := md5Print(md5String(uName + ':' + realm + ':' + aPwd));                     h2 := md5Print(md5String(aMethod + ':' + aUri));                     h3 := md5Print(md5String(h1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + h2 ));                     MozillaHeaders(ht);                     ht.Cookies.Text := aCookies.Text;                     ht.Headers.Add('Authorization: Digest username=' + AnsiQuotedStr(uName,#34) + ', realm=' + AnsiQuotedStr(realm, #34) + ', nonce=' + AnsiQuotedStr(nonce,#34) + ', uri=' + AnsiQuotedStr(aUri, #34) + ', qop=' + qop + ', nc=' + nc + ', cnonce=' + AnsiQuotedStr(cnonce, #34) + ', response=' + AnsiQuotedStr(h3, #34));                     isOk := ht.HTTPMethod(aMethod, edtUrl.Text);                     Break;                 end;             end;         end;         if IsOk then         begin             ht.Document.Seek(0,0);             memBody.Lines.LoadFromStream(ht.Document);         end;         memHeader.Text := IntToStr(ht.ResultCode) + ' ' + ht.ResultString + #13 + ht.Headers.Text;         oldNonce := nonce;         oldcNonce := cnonce;     finally         ht.Free;         aList.Free;         aCookies.Free;     end; end; And a few helper functions: Code: [Select]procedure TForm1.FormCreate(Sender: TObject); begin     intNC := 1; end; function TForm1.StripQuotes(strIn: String): String; begin     Result := StringReplace(strIn,'"','',[rfReplaceAll]); end; procedure TForm1.MozillaHeaders(var aHttpSend: THttpSend); begin     aHttpSend.Clear;     aHttpSend.Headers.Clear;     aHttpSend.Protocol := '1.1';     aHttpSend.Headers.Add('Connection: keep-alive');     aHttpSend.Headers.Add('User-Agent: Mozilla/5.0 (X11; Linux i686; rv:9.0) Gecko/20100101 Firefox/9.0');     aHttpSend.Headers.Add('Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8');     aHttpSend.Headers.Add('Accept-Language: en-us,en;q=0.5');     aHttpSend.Headers.Add('Accept-Encoding: gzip, deflate');     aHttpSend.Headers.Add('Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'); end; Without pumping in all those headers it was returning a 400 Bad Request.  Well. I had to go through hoops to get it to work but the effort was worth it. Let's hope with a little refinement it will find itself into the official Synapse code-base.  Cheers everyone. Best regards everyone JamHitz ---------------------------------------------------------------------- ludob · 2012-01-12 08:02 QuoteFinally!!Well done A few suggestions for improvement: - replace Code: [Select]if (ht.ResultCode = 401) and (UpperCase(ht.ResultString)='UNAUTHORIZED') thensimpy byCode: [Select]if (ht.ResultCode = 401)  thenCode 401 means 'unauthorized' and the html string can be translated into other languages. - the unit synautil has function NormalizeHeader and function SeparateRight that help you to parse the header. An example of its usage can be found in httpsend line 613 and further. You are assuming the header received has a fixed format which is not always the case. These helpers will deal with that. BTW you send yourself a authorization header to the server which doesn't respect that fixed format - create a class to group your routines and allow for re-use of the nonce for subsequent pages. ---------------------------------------------------------------------- jamhitz · 2012-01-13 07:19 Quote from: ludob on January 12, 2012, 09:02:05 amWell done Thank you. QuoteBTW you send yourself a authorization header to the server which doesn't respect that fixed format I'm a lost here.  Please explain. Quote- create a class to group your routines and allow for re-use of the nonce for subsequent pages. Will do. ---------------------------------------------------------------------- ludob · 2012-01-13 09:43 Quote from: jamhitz on January 13, 2012, 08:19:02 amQuoteBTW you send yourself a authorization header to the server which doesn't respect that fixed format I'm a lost here.  Please explain. [/quote] I looked again at the code and apparently I overlooked one of the many intermediate steps. You do respect your own assumptions  Your most risky assumption is probably that the WWW-AUTHENTICATE: DIGEST is sent on one line.