How to log in in JSON or XML-RPC?

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!!!

  1. try to not raise exception. in this event you can manually write server response into aResponseStream.
  2. have you saw that snippet? you need to put your error into aResponseStream.

Hi Evgeny,

so, I have tried to change the stream, but I have following issues:

  1. The streams are empty, i.e. aResponseStream.Size = 0 and aRequestStream.Size = 0

What should they contain!?

  1. I have tried to write on this way:
> procedure TMyROIndyHTTPServer.OnCustomResponseEvent(const aTransport: IROHTTPTransport; const aRequestStream,
>     aResponseStream: TStream; const aResponse: IROHTTPResponse; var aHandled: Boolean);
> var
>   lMessage:  IROMessage;
>   op: TROResponseOptions;
>   s: AnsiString;
> begin
>   // * extracting data if POST method was used
>   SetLength(s, aRequestStream.Size);
>   aResponseStream.Position := 0;
>   aRequestStream.Read(Pointer(s)^, Length(s));
>  // modify data in some way
>   s := 'Status: 401 '+HTTP_401_status+s;   // <---- I DON'T THINK THIS IS CORECCT. COULD YOU HELP ME HERE WITH THE CODE?
>  // write updated data back to stream
>   aRequestStream.Size := 0;
>   aRequestStream.Write(Pointer(s)^, Length(s));
> //  lMessage:= (FJSONMessage as IROMessageCloneable).Clone;  // <--- WHAT KIND OF MESSAGE SHOULD I PROVIDE HERE?! I am working in my server with BIN, JSON and XML. How do I find out which message is triggered by client?!
> //  MainProcessMessage(lMessage,atransport,aRequestStream,aResponseStream,op);
>   aHandled := True;
> end;

Please see my questions in the formatted code!

you can use something like


 procedure TMyROIndyHTTPServer.OnCustomResponseEvent(const aTransport: IROHTTPTransport; const aRequestStream,
     aResponseStream: TStream; const aResponse: IROHTTPResponse; var aHandled: Boolean);
const 
  sJsonError: AnsiString = '{"version":"1.1","error":{"name":"JsonRPCError","code":"401","message":"Error"}}';
 var
   lMessage:  IROMessage;
   op: TROResponseOptions;
   s: AnsiString;
   lMyMessage: TROMessage;
begin
  if pos('/xjson',aTransport.PathInfo) = 1 then lMyMessage := ROJSONMessage1
  else if pos('/xbin',aTransport.PathInfo) = 1 then lMyMessage := ROBinMessage1
  else ....

  lMessage:= (lMyMessage as IROMessageCloneable).Clone;
  try
    MainProcessMessage(lMessage,atransport,aRequestStream,aResponseStream,op);
  except
    on E: MyError do begin
      aResponse.Code := HTTP_401_code;
      aResponse.Status := HTTP_401_status;
      if lMyMessage = ROJSONMessage1 then
        aResponseStream.Write(sJsonError[1], Length(sJsonError) * SizeOf(AnsiChar));
      else
	 .....
    end;
    on E: Exception do raise;
  end;

   aHandled := True;
 end;

Note: may contains typo

Your code works almost perfect! :wink: The problem is that my custom exception from a Service-RODataModule is catched and handled somewhere in MainProcessMessage(…). So, on E: MyError do begin can’t be reached, it goes directly to aHandled := True;

Some workaround for that? :slight_smile:

you can create a replica of MainProcessMessage and handle exception in OnCustomResponseEvent instead of

Okay, I will try it out!

But! Evgeny, thanks a lot for your prompt and helpful answers!

I know now much more then I wanted to know originally! :smiley: