TWebClientConnection and Basic Authentication

Hello!

I'm accessing a REST API that requires BASIC authentication. I use TWebClientConnection to access the API. When for testing purposes the authentication is disabled at the server side, TWebClientConnection works and returns the expected data. With authentication enabled, I am unable to get it to work. Although I'm a Delphi programmer "only" and somewhat expected that TMS Web Core would hide that nasty HTML/HTTP jadda-jadda from me, I understand that when it comes to authentication the component doesn't support me and I have to fiddle with headers myself :-((
I found out that for Basic Authentication I need a header like this: "Authorization: Basic YXBpdXNlcjp0cmFsbGFsYQ==", where the "YXBpdXNlcjp0cmFsbGFsYQ==" is the base64 encoded username:password . This I seem to have to enter in the Header stringlist property of the component like this: "Authorization=Basic YXBpdXNlcjp0cmFsbGFsYQ==".

But it doesn't work. The HTML result is "401" and the component fires the OnConnectError event with a meaningless ErrorCode of 0.

Using a local XAMPP stack and Delphi's RESTDebugger with Basic Authentication enabled, it works! I do receive the expected JSON data. If I just enter the URL "http://localhost/api.php/records/User" in the browser, it also works. Both ways, using the browser debugger I see these headers:

GET /api.php/records/User HTTP/1.1
[...]
Authorization: Basic YXBpdXNlcjp0cmFsbGFsYQ==
[...]

Instead, when using the Web Core component with the above authorizaton header added to the Header property, the headers visible in the browser debugger change to this:

OPTIONS /api.php/records/User HTTP/1.1
[...]
Access-Control-Request-Headers: authorization
[...]

Main thing I see here is that "GET" changed to "OPTION". I assume this is wrong. Then, regarding the authorization, the expected "Authorization=Basic YXBpdXNlcjp0cmFsbGFsYQ==" doesn't come through.

I also tried adding the header in code like this:

Con.Headers.Values['Authorization'] := 'Basic YXBpdXNlcjp0cmFsbGFsYQ==';
Con.Active := True;

With the same results.

So, what is the problem here? How do I get BASIC authentication to work with TWebClientConnection (or also TWebHttpRequest). It shouldn't be that hard...

I have insufficient information about what exactly your server expects. I also wonder whether CORS is enabled on the server? This is a prerequisite for web clients.
Without sufficient details, it is very hard to guess what is happening on your side.

As said, the REST Api can successfully be accessed using the REST Debugger that comes with Delphi as well as when just using a simple browser. Both with Basic Authentication. Only when using the WEB Core component it fails as described above. What else can I do to proof that the Web Server and the REST Api as such are Ok?

May be I'm using the component the wrong way with regards to Basic Authentication. But unfortunately, the manual doesn't explain anything about this and I also could not find a demo that uses Basic Authentication.

Can you assure it runs on your side? Can you further elaborate on how to correctly enter User/Password into the component to use Basic Authentication?

Did you inspect the browser console to see what exact error messages you get?
Did you verify this isn't a CORS issue on server side?

When authentication at the REST server side is disabled (.htaccess/.htpasswd deleted), the component works! So, no CORS issue. BUT, as soon as I put something in either User/Password of the component or add the authentication header like Con.Headers.Values['Authorization'] := 'Basic YXBpdXNlcjp0cmFsbGFsYQ==';, the request does indeed run into that dreaded "blocked by CORS" error.

So the question now changes to:

Why do I have no CORS problem when I do not enter any credentials in the component and why do I have a CORS problem when I do enter something?

What server side technology do you use?
CORS is a server side configuration, so if you get a CORS error, it must be investigated server side what incorrect configuration is causing this.

RANTING: ON

After again fiddling with this issue for many hours, I have to admit that you are right that it all comes down to that dreaded CORS thing and how to get rid of it. But at the moment, our team is fed up now experimenting with gazillions of "guaranteed to work" header manipulation suggestions from the internet.

So, for the time being, this CORS issue effectively blocks our experiments with Web Core and we, as being all long time Delphiers, first have to search for someone with that Web header fiddling expertise.

Might be an idea for TMS to provide a tutorial for Delphiers with best practices on how to handle CORS for popular web servers like Apache/IIS, in particular which files to fiddle with and what headers to add where that "really" are guaranteed to work.

This might be a good starting point:
https://www.w3.org/wiki/CORS_Enabled

We read this, but it didn't help us. We need to leave this for an expert in these nasty security issues...

But we just decided that Web Core definitely is a very interesting product and we want to follow that road a little further. So for the duration of our tests, the server guys deleted the .htaccess which solves the CORS issue.

There is just one thing. How can we catch a CORS error once it is activated again. The OnError event isn't very helpful as ARequest.req.Status always returns 0. Further inspection of AResponse in the browser debugger also gives no enlightment as to what exactly caused the error.

Is there any way to get the reason why OnError fired?

The OnError event maps on the XMLHttpRequest JavaScript onerror event:

For the most detailed error reporting, we suggest to inspect the browser console.

Hello Bruno!

I tried several options I found on TMS support videos about Basic Authentication issue. Walter was asking for a demo using Basic Authentication and I second his request. Now, 3 years later we still can't find a decent example to use with the TMS webcore TWebHTTPRequest component using any type of authentication. Can you please help? Thank you in advance!

Basic authentication is simply adding the (simple) basic authentication header to WebHttpRequest.Headers.
The specification is explained here:

Example from TMS WEB Core:

var
  usr,pwd,hdr: string;
begin
  usr := webedit_username.Text;
  pwd := webedit_password.Text;
  hdr := 'Authorization: Basic ' + StringToBase64(usr+':'+pwd);
  WebHttpRequest1.Headers.Add(hdr);
end;
1 Like

Thank you Bruno! Does basic Authentication work with TMS web server? If I send a get request over Postman with basic authentication on, the request gets a response, if I send the same request from within my TMS project, my request never hits the server.

I'm not sure what kind of request you send this way to the TMS debug web server and what you want to use it for? The TMS debug web server has no authentication built-in. It is just for debugging purposes to run TMS WEB Core web client applications from the IDE.

Maybe this helps as example with Basic Authentication.
I also spend many hours on Corrs issues.
The reason that I clear the WebHttpRequest1 headers is because I get errors if I don't.

"has been blocked by CORS policy: Request header field cache-control is not allowed by Access-Control-Allow-Headers in preflight response."

But that could just be the webservice I use.

procedure TForm1.WebButton1Click(Sender: TObject);
var
  req: TJSXMLHttpRequest;
begin
  WebHttpRequest1.Headers.Clear;
  WebHttpRequest1.User:=user;
  WebHttpRequest1.Password:=ww;
  WebHttpRequest1.URL := 'https://xxxxxx.nl:8087/api/helloworld';
  req := await(TJSXMLHttpRequest, WebHttpRequest1.Perform());

  await(TModalResult, WebMessageDlg1.ShowDialog(String(req.response), WEBLib.Dialogs.mtInformation, [mbOK]));
end;

If https://xxxxxx.nl:8087 is your own service and the TMS WEB Core app runs from another domain (even when just the port is different), you'll need to CORS enable your server app.
So, it is in the backend you need to look, not in the TMS WEB Core frontend app.