How to log in in JSON or XML-RPC?

Hello,

I am using Delphi RO SDK

How can I let a user send a JSON-RPC or XML-RPC request for a service which needs a login?

{
	"version": "1.1",
	"method": "MyService.GetSecureCompanyData",
	"params": {
		"DataID": "1234"
	}
}

Of course I can pass a username and password to the method, but then I should pass these parameters to each service method which I have? And I dont’t want to do this. I would like to login only once and call all methods of a service.

Could you help me? Thank you!

Oooohh! So stupid! Sorry for that stupid question! I can just use the http header variables, but the right way would be to use the basic http authentication.

So, the question is answered I think! :slight_smile:

And on the server I can read the login data in RORemoteDataModuleGetDispatchInfo e.g.

procedure TMyService.RORemoteDataModuleGetDispatchInfo(const aTransport: IROTransport; const aMessage: IROMessage);
var
  TCPInfo:IROTCPtransport;
  HTTPInfo:IROHTTPTransport;
  Authorization: string;
begin
    if Supports(aTransport, IROHTTPTransport, HTTPInfo) then
    begin
      Authorization := HTTPInfo.Headers['Authorization'];
      // ... decode Authorization
    end;
end;

Am I correct? Would it be the right way to handle such a authentification?

Each HTTP server (delphi) has these properties/event:

  • HTTPAuthenticationRealm
  • RequireHTTPAuthentication
  • OnHTTPAuthentication

so you can so checking in HTTP server itself.

If I use the “basic http authorization”, I can get a base64 encoded string from http headers. But I the properties/events are empty or are not called.

Could you provide an example how I can read them!? And at what point of time OnHTTPAuthentication is trigered?

have you set RequireHTTPAuthentication to true?
server should return 401 code if authorization is failed/isn’t found.
your client-side should catch this code and provide correct info.

you can create simple delphi client and review how server-side works.

Ok, I tried this and it works.

Another question, I dont’t want the authorization for all services of a server, I want to have it only for some specific services.

Is it possible in a RODataModule? Or should I check it through the http headers? If so, how can I return 401 code in RODataModule?

you can use Session feature. it allows to “protect” some services.
“Protected” services should have RequiresSession = True and assigned SessionManager.
Note: LoginService should have RequiresSession = False, otherwise clients can’t access to it.

See the Session Types example and Login and Authentication article for details

Ok, I can use session, but I can also pass user/password in the http headers and read it in RORemoteDataModuleGetDispatchInfo of a Service-RODataModule. Right?

So, I can check the http headers and if I don’t find user/password I want to send back the 401 HTTP status code. Is it possible to do? Here is an example

procedure TMyService.RORemoteDataModuleGetDispatchInfo(const aTransport: IROTransport; const aMessage: IROMessage);
var
  TCPInfo:IROTCPtransport;
  HTTPInfo:IROHTTPTransport;
  Authorization: string;
begin
    if Supports(aTransport, IROHTTPTransport, HTTPInfo) then
    begin
      WWWAuthenticate := HTTPInfo.Headers['WWW-Authenticate'];
      Authorization := HTTPInfo.Headers['Authorization'].Substring(Length('Basic')+Pos('Basic ', HTTPInfo.Headers['Authorization']));
      Authorization_Decoded := Soap.EncdDecd.DecodeString(Authorization);
      if Authorization_Decoded  <> 'I am OK'  then
      begin
          // how to return here 401 status code?! 
      end;  
    end;
end;

try to cast aTransport to IROHTTPRequest as you did for IROHTTPTransport.
also this method should fall with exception otherwise execution will be continued.

:slight_smile: tested! It works! Thanks! Buuuuut, my question remains :slight_smile:
If I raise an exception the client gets the http status code 200 OK but with an error. And I would like to return the code 401. How can I do this?

have you set 401 in IROHTTPRequest ?

I did this:

  if Supports(aTransport, IROHTTPRequest, HTTPRequestInfo) then
  begin
    UsesAuthentication := HTTPRequestInfo.UsesAuthentication;
    AuthUsername := HTTPRequestInfo.AuthUsername;
    AuthPassword := HTTPRequestInfo.AuthPassword;

    if AuthUsername <> 'I am OK' then
    begin
      HTTPRequestInfo.Headers['Status'] := 'Status: 401 Unauthorized';
      raise Exception.Create('Unauthorized');
    end;
  end;

The result is with the Status: 200 OK
{
“version”: “1.1”,
“error”: {
“name”: “JsonRPCError”,
“code”: “1”,
“message”: “Unauthorized”
}
}

sorry, I mistyped. I mean IROHTTPResponse
you need

        aResponse.Code := HTTP_401_code;
        aResponse.Status:= HTTP_401_status;

:slight_smile: I tried this and the transport doesn’t support the interface!?

  if Supports(aTransport, IROHTTPResponse, HTTPResponseInfo) then
  begin
    http_code := HTTPResponseInfo.Code;
  end;

So, I can not reach the code row :-/

(aTransport as IROHTTPResponse).Code := HTTP_401_code; says “interface is not supported”

you can’t do this with std implementation, but you can reach this with HTTPServer.OnCustomResponseEvent.
just raise custom error and catch it inside this event and pass into aResponse.

How do I fire this event!? When is it executed?

I have created an dispatch event and assigned it to the server:

procedure OnCustomResponseEvent(const aTransport: IROHTTPTransport; const aRequestStream,
    aResponseStream: TStream; const aResponse: IROHTTPResponse; var aHandled: Boolean);
begin
  aResponse.Code := 401;
end;


  FServer.OnCustomResponseEvent := OnCustomResponseEvent;
  FServer.Active := True;

And if I raise an exception in my service, the event is not called!

this event is fired when unnamed dispatcher is found.
from that example:

  // handle all requests started with 'xxxx':
  if pos('/xxxx',aTransport.PathInfo) = 1 then begin  // "xxxx" shouldn't be defined in Server.Dispathers

so client-side should sent request to http://localhost:8099/xxxx

okay, thanks, you helped me, but I have still the problems :frowning:

  1. I can’t use the standard JSON/XML-RPC dispatcher

  2. I can change the response status text, but not the response code:

     procedure TROIndyHTTPServer.OnCustomResponseEvent(const aTransport: IROHTTPTransport; const aRequestStream,
         aResponseStream: TStream; const aResponse: IROHTTPResponse; var aHandled: Boolean);
     begin
       aResponse.Code := HTTP_401_code;
       aResponse.Status := HTTP_401_status;
       aHandled := True;
       raise Exception.Create('Your login failed!');
     end;
    

The http result

200 Unauthorized

<font size="7">Invalid Path</font>
<br />Your login failed!

Questions #1: How can I change the code?
Questions #2: How can I modify the result body in order to send a JSON-RPC error message!?

Thank you for your help!!!