[SOLVED] Ouath 2 with LinkedIn and TFPHTTPServer lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- tudi_x · 2017-07-11 12:38 hi All, i am getting a 'Missing HTTP protocol version in request' when trying to extract the values (code and state) from the HTTP request from the LinkedIn server with the code attached. as per the request i see http protocol it is 1.1. please advise what i am missing. when i am making the request to my code with a rest client (Insomnia) i do not get an error. i also analyzed the request from LinkedIn with requestb.in and it was parsed with no issues by them. thank you using Lazarus 1.6.4 on Windows 7 64 bit you would need libeay32.dll and ssleay32.dll in same directory as exe ---------------------------------------------------------------------- rvk · 2017-07-11 13:30 I'm not sure what you are expecting but you can't retrieve the access- token with just one "get". I see you do the t.AGet() and directly try to access the html from it. But OAuth2 doesn't work that way. If you look closely you'll see that your code runs through the t.AGet() before asking the user for permission. It shows you an access-page in the browser. The users needs to allow the access first and after that the browser is redirected to the redirect_uri. AFTER that you need to read the URI to extract the code it gives you. (you can see that after pressing allow you get redirected to localhost with a code in the URL) You can proceed with that after that. (This should be the same with Google OAuth2). The only way to monitor the URL is to open a browser and wait for the browsers-title page to change. You can see here how I did that with Google OAuth2: https://github.com/rvk01/google-oauth2 Look at TGoogleOAuth2.GetAuthorize_token_interactive in https://github.com/rvk01/google- oauth2/blob/master/google_oauth2.pas#L474 ---------------------------------------------------------------------- tudi_x · 2017-07-11 14:01 i actually started from your repository some days ago. it was very helpful ! thank you rvk. unfortunately your code is not cross platform. my idea is to have a socket opened on localhost so the redirect URL would actually do a GET to this URL. this socket starts with form create in a separate thread. this way on redirect (after the user provides the ALLOW) the browser is doing a GET on my socket with the parameters i need. during the synchronize i catch these parameters and i use them in the next steps of the ouath 2 process in order to get the access and refresh token. so to answer i am not trying to get with this code the access token. what i am trying to get is the authorize code. yes, the code runs through because i do not have access to the browser. i just start the browser and it is on it's own - this is different than your solution where you control the browser. please note that there is no AFTER that for me - it is just a listening socket that is always there and gets a request. because i do not control the browser i do not know when the user pressed ALLOW, what i know is just when the redirect hits my socket. besides this ouath 2 issues please advise on the missing protocol error if possible. thank you ---------------------------------------------------------------------- rvk · 2017-07-11 14:13 Woops, yeah, I missed the server part you were doing  The problem you are having is that you get 2 request right after each other. And in the HandleHTTPRequest() you do a Memo1.Clear; Change the following and you'll see the redirect: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   // Memo1.Clear;  Memo1.Append('------');  Memo1.Append('starting...');  Memo1.Append('request URI: ' + trim(ARequest.URI));  Memo1.Append('request URL: ' + trim(ARequest.URL));  Memo1.Append('request: ' + trim(ARequest.ContentFields.Text));  Memo1.Append('http version: ' + ARequest.HttpVersion);  Memo1.Append('protocol version: ' + ARequest.ProtocolVersion); Gives me this: Code: [Select]starting... request URI: /?code=AQT-iDkodSGx4O7SoiZ3nZd5_Qkwj6xxx3pN0WbrAZI- ixkAJVfTcm5KBJ1QBtx0gqQZ2I3XqSE8pZD-3J1Wvc- ZHRjSu3jhMW6KEBLwkh5ciJSa4Vk&state=T1499789434 request URL: /?code=AQT-iDkodSGx4O7SoiZ3nZd5_Qkwj6xxx3pN0WbrAZI- ixkAJVfTcm5KBJ1QBtx0gqQZ2I3XqSE8pZD-3J1Wvc- ZHRjSu3jhMW6KEBLwkh5ciJSa4Vk&state=T1499789434 request: http version: 1.1 protocol version: 1.1 server error: nil stopping... ------ starting... request URI: /favicon.ico request URL: /favicon.ico request: http version: 1.1 protocol version: 1.1 server error: nil stopping... See the request URI in the first request? You can extract the code from that. The second request is for /favicon.ico (for which you don't have any use) ---------------------------------------------------------------------- tudi_x · 2017-07-11 14:28 thanks a lot! please advise how i could actually extract the values from the valued GET. i see that ARequest.ContentFields.Count is zero. i can of course parse directly the ARequest.URL but i thought i could use some help from the http methods. ---------------------------------------------------------------------- molly · 2017-07-11 14:44 Quote from: tudi_x on July 11, 2017, 04:28:59 pmi can of course parse directly the ARequest.URL but i thought i could use some help from the http methods. Isn't that what QueryFields (TRequest/THTTPHeader) was meant for ? I'm (wrongfully?) assuming that the used field is always named code ? ---------------------------------------------------------------------- rvk · 2017-07-11 14:45 Quote from: tudi_x on July 11, 2017, 04:28:59 pmplease advise how i could actually extract the values from the valued GET. i see that ARequest.ContentFields.Count is zero. I think the ContentFields are only filled if you get a POST request. Synapse will extract from FORM-DATA or X-WWW-FORM-URLENCODED. At least, I only see the filling of ContentFields in InitPostVars in HTTPDefs.pas. And: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   if (CompareText(R,'POST')=0) or (CompareText(R,'PUT')=0) or (ContentLength>0) then    InitPostVars; So you don't get a POST but just a redirect with GET. Synapse does extract these in the Request in QueryFields. So the only thing you need to change is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   setlength(_HTTPRequestInfo, ARequest.QueryFields.Count);  for i := 0 to ARequest.QueryFields.Count - 1 do  begin    _HTTPRequestInfo[i].FieldName := Copy(ARequest.QueryFields[i], 1, Pos('=', ARequest.QueryFields[i]) - 1);    _HTTPRequestInfo[i].FieldValue := Copy(ARequest.QueryFields[i], Pos('=', ARequest.QueryFields[i]) + 1, length(ARequest.QueryFields[i]));  end; edit: molly just beat me by seconds. That's what you get for typing a whole solution  Do note that the second pass (for favicon.ico) to HandleHTTPRequest will delete your own array again in setlength(_HTTPRequestInfo); So you need to save it or build in a check for Count > 0. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i if ARequest.QueryFields.Count > 0 thenbegin  setlength(_HTTPRequestInfo, ARequest.QueryFields.Count);  for i := 0 to ARequest.QueryFields.Count - 1 do  begin    _HTTPRequestInfo[i].FieldName := Copy(ARequest.QueryFields[i], 1, Pos('=', ARequest.QueryFields[i]) - 1);    _HTTPRequestInfo[i].FieldValue := Copy(ARequest.QueryFields[i], Pos('=', ARequest.QueryFields[i]) + 1, length(ARequest.QueryFields[i]));  end;end; ---------------------------------------------------------------------- tudi_x · 2017-07-11 14:51 much obliged i guess 'Missing HTTP protocol version in request' could just be ignored ... ---------------------------------------------------------------------- rvk · 2017-07-11 14:54 Quote from: tudi_x on July 11, 2017, 04:51:54 pmi guess 'Missing HTTP protocol version in request' could just be ignored ... I'm not getting that. Is that a message from your browser in Linux? In that case it might be useful to send a full header-set in your httpserver to the browser. Maybe with a message in html that granting access is successful (or is not successful). Now the user gets a blank page. ---------------------------------------------------------------------- molly · 2017-07-11 14:58 Quote from: rvk on July 11, 2017, 04:45:11 pmedit: molly just beat me by seconds. That's what you get for typing a whole solution  Sorry. it was not mean to intrude on your conversation. I just happen to cross the subject on a recent project i worked on. Your answer is by miles better than what i was able to come up with, as i'm (still) exploring fcl http classes, so thank you very much for the elaborated/illustrated answer. ---------------------------------------------------------------------- tudi_x · 2017-07-11 15:00 i am getting the message when debugging the app. please see the screen capture. there is something similar reported in http://lists.freepascal.org/pipermail/fpc- pascal/2014-April/041701.html i think it is the same because they are speaking of a localhost socket. ---------------------------------------------------------------------- rvk · 2017-07-11 15:07 Quote from: molly on July 11, 2017, 04:58:58 pmQuote from: rvk on July 11, 2017, 04:45:11 pmedit: molly just beat me by seconds. That's what you get for typing a whole solution  Sorry. it was not mean to intrude on your conversation. I just happen to cross the subject on a recent project i worked on. No problem at all. I've expanded the request handler somewhat to also output success or failure to the browser so the user can see what's going on: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.HandleHTTPRequest(Sender: TObject; var ARequest: TFPHTTPConnectionRequest; var AResponse: TFPHTTPConnectionResponse);var  i: integer;  match: boolean;begin  // Memo1.Clear;  Memo1.Append('------');  Memo1.Append('starting...');  Memo1.Append('request URI: ' + trim(ARequest.URI));  Memo1.Append('request URL: ' + trim(ARequest.URL));  Memo1.Append('request Queryfields: ' + trim(ARequest.QueryFields.Text));  Memo1.Append('http version: ' + ARequest.HttpVersion);  Memo1.Append('protocol version: ' + ARequest.ProtocolVersion);   if ARequest.QueryFields.Count > 0 then  begin    setlength(_HTTPRequestInfo, ARequest.QueryFields.Count);    for i := 0 to ARequest.QueryFields.Count - 1 do    begin      _HTTPRequestInfo[i].FieldName := Copy(ARequest.QueryFields[i], 1, Pos('=', ARequest.QueryFields[i]) - 1);      _HTTPRequestInfo[i].FieldValue := Copy(ARequest.QueryFields[i], Pos('=', ARequest.QueryFields[i]) + 1, length(ARequest.QueryFields[i]));    end;  end;   _HTTPServer.Synchronize(nil, @HandleHTTPInfo);   // here you give a response back to the browser  match := false;  for i := 0 to length(_HTTPRequestInfo) - 1 do    if _HTTPRequestInfo[i].FieldName = 'code' then match := true;   if match then    AResponse.Content := 'Yeah.........' // Only if successful :)  else    AResponse.Content := 'Noooooo .....'; end; Of course the AResponse.Content needs to be a complete HTML so it's a but prettier then only one line. It might be that the server also sends the HTTP-version automatically. tudi_x, do you still get the error when using AResponse.Content? At what point do you get the error. After pressing Allow or before that? ---------------------------------------------------------------------- tudi_x · 2017-07-11 15:11 i get the error after pressing the ALLOW. i can also see by then the redirect has taken place. sorry there is no HTML for the user, this is just a prototype for ouath 2. only after reaching the end of ouath i would be thinking about the user experience . ---------------------------------------------------------------------- rvk · 2017-07-11 15:14 Quote from: tudi_x on July 11, 2017, 05:11:20 pmsorry there is no HTML for the user, this is just a prototype for ouath 2. only after reaching the end of ouath i would be thinking about the user experience . Well, adding the few lines I showed already gives the user some feedback. You can paste in some more HTML later on. Does the Memo1 show the HTTP version and protocol for the request? The link you gave talks about Windows and I'm on Windows and don't have the error. (although my version is 1.8RC2 with FPC 3.1.1) It looks like your browser doesn't send HTTP/1.1 or 1.0. (maybe only for localhost) You could try 127.0.0.1 as redirect instead of localhost? ---------------------------------------------------------------------- tudi_x · 2017-07-11 15:23 i am in 1.6.4. the contents of my memo is below. i am using Firefox Developer Edition as browser. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ------starting...request URI: /?code=AQT1TPhn7tWtHGVTx-pnoomAEL4uFYY0B 4k7c70BqRzBKrmrsChEeG2yjUuRCQOtkYOwxcBjjI0YXO0s1xXOWJJLvceo1XxlQTZpgxg f7mpN2mH1Zcg&state=T1499797252request URL: /?code=AQT1TPhn7tWtHGVTx-pn oomAEL4uFYY0B4k7c70BqRzBKrmrsChEeG2yjUuRCQOtkYOwxcBjjI0YXO0s1xXOWJJLvc eo1XxlQTZpgxgf7mpN2mH1Zcg&state=T1499797252request Queryfields: code=AQT1TPhn7tWtHGVTx-pnoomAEL4uFYY0B4k7c70BqRzBKrmrsChEeG2yjUuRCQOtk YOwxcBjjI0YXO0s1xXOWJJLvceo1XxlQTZpgxgf7mpN2mH1Zcgstate=T1499797252htt p version: 1.1protocol version: 1.1field name:state value:T1499797252 ------starting...request URI: /favicon.icorequest URL: /favicon.icorequest Queryfields: http version: 1.1protocol version: 1.1 ---------------------------------------------------------------------- tudi_x · 2017-07-11 15:27 with 127.0.0.1 i am not getting the missing protocol message! ---------------------------------------------------------------------- rvk · 2017-07-11 15:27 I take it you stripped out the "field name:code value" line? Because I can see you get a code back but I don't see the corresponding field name line. My result: Code: [Select]------ starting... request URI: /?code=AQQ1fbiK5thDm1gzhFsAh0dy3Pc83s59T6am7Zi13- PnwLsvMzv_vFtq6Q03-5qVlLHzMF5CNgGhFT4CucWRjBhna9hBc9c9ogAHZMq- QOfkSadJ3sk&state=T1499793870 request URL: /?code=AQQ1fbiK5thDm1gzhFsAh0dy3Pc83s59T6am7Zi13- PnwLsvMzv_vFtq6Q03-5qVlLHzMF5CNgGhFT4CucWRjBhna9hBc9c9ogAHZMq- QOfkSadJ3sk&state=T1499793870 request Queryfields: code=AQQ1fbiK5thDm1gzhFsAh0dy3Pc83s59T6am7Zi13- PnwLsvMzv_vFtq6Q03-5qVlLHzMF5CNgGhFT4CucWRjBhna9hBc9c9ogAHZMq- QOfkSadJ3sk state=T1499793870 http version: 1.1 protocol version: 1.1 server error: nil field name:code value:AQQ1fbiK5thDm1gzhFsAh0dy3Pc83s59T6am7Zi13- PnwLsvMzv_vFtq6Q03-5qVlLHzMF5CNgGhFT4CucWRjBhna9hBc9c9ogAHZMq- QOfkSadJ3sk field name:state value:T1499793870 stopping... ------ starting... request URI: /favicon.ico request URL: /favicon.ico request Queryfields: http version: 1.1 protocol version: 1.1 server error: nil field name:code value:AQQ1fbiK5thDm1gzhFsAh0dy3Pc83s59T6am7Zi13- PnwLsvMzv_vFtq6Q03-5qVlLHzMF5CNgGhFT4CucWRjBhna9hBc9c9ogAHZMq- QOfkSadJ3sk field name:state value:T1499793870 stopping... You see for both request the field name code and state. (You need to do the check for >0 of count otherwise your variable is overwritten. Anyway... In both requests I see a http version 1.1 and protocol 1.1 So I'm not sure where the error comes from. ---------------------------------------------------------------------- rvk · 2017-07-11 15:28 Quote from: tudi_x on July 11, 2017, 05:27:22 pmwith 127.0.0.1 i am not getting the missing protocol message! Ah, Ok. In that case it might be a "bug" in Firefox developer that the HTTP/1.1 is not correctly send with localhost. Or maybe the first line is empty and the second is HTTP/1.1 while that HTTP/1.1 should always be the exact first line. ---------------------------------------------------------------------- tudi_x · 2017-07-11 15:32 i am extracting the authorization code with the below: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.HandleHTTPInfo;var  i: integer; begin  for i:= Length(_HTTPRequestInfo) - 1 downto 0 do  begin    Memo1.Append('field name:' + _HTTPRequestInfo[i].FieldName + ' value:' + _HTTPRequestInfo[i].FieldValue);    if  _HTTPRequestInfo[i].FieldName = 'state' then begin      if _HTTPRequestInfo[i].FieldValue = _Salt then begin        if (i - 1) >= 0 then begin          if _HTTPRequestInfo[i-1].FieldName = 'code' then begin            _AuthorizeCode:=_HTTPRequestInfo[i-1].FieldValue;            Memo1.Append('authorize code:' + _AuthorizeCode);            break;        end;        end;      end;    end;  end;end; ---------------------------------------------------------------------- rvk · 2017-07-11 15:39 Quote from: tudi_x on July 11, 2017, 05:32:33 pmi am extracting the authorization code with the below: Yes, that should work. B.T.W. you mentioned adding the openssl dll's to the directory. Quote from: tudi_x on July 11, 2017, 02:38:36 pmusing Lazarus 1.6.4 on Windows 7 64 bit you would need libeay32.dll and ssleay32.dll in same directory as exe But for just the HTTP server / authentication part you won't need these yet. You only need them when doing https. But my guess is that you will be doing the further communication via fphttp components for which you need https access. First (and hardest) part is over