You are not logged in.
Pages: 1
Hi @ab,
I use interfaced based service with session for our applications, but for external integration I need that the user can invoke such services but with JWT, that is, using JWT for validate the request without existing session. Is this posible ?
Thanks.
Esteban
Offline
Currently, JWT is not integrated with mORMot sessions.
Either you use the regular mORMot authentication classes - with URI signature - or you use a JWT.
But you could extract any information stored within the JWT when calling a method-based or interface-based services, from the execution context.
So in practice, what we do is:
1. use mORMot sessions for internal Delphi/FPC client communication
2. use JWT for public API, which won't use mORMot sessions, but a dedicated authentication service which returns a JWT for a given user
Offline
Ok, I can confirm that when calling an interface based service without session signature the invokation never arrives to the service implementation because Ctxt.Authenticate reject by invalid signature. This not allow invoke interface based service with a valid JWT. May be I'm doing something wrong.
If I implement a method based service that return a valid JWT, how invoke then interface based service with these JWT ?
Thanks.
Esteban
Offline
You can call an interface-based service with a valid JWT using TSQLRestServer.JWTForUnauthenticatedRequest.
The only restriction is that the interface is should be defined as sicShared or sicSingle, since there is no mORMot user (nor session) associated with the JWT.
Offline
I have created the TSQLRestServer.JWTForUnauthenticatedRequest and the interface is defined as sicShared.
For example, this is my code excerpt in the REST server creation:
TARIS.Run // TARIS is a container 
  ...
  // 6. Se crea el servidor REST para poder hacer migraciones si es necesario
  // - HandleAuthentication debe ser true para poder manejar usuarios
  fRestServer := TARISRestServer.Create(fModel, fARISDB, fConfig.RESTServer.HandleAuthentication);
  fRestServer.ServicesRouting := TSQLRestRoutingREST_JWT;
  // - Se permite autenticación/autorización básica (Authorization: Basic ...)
  fRestServer.AuthenticationRegister(TSQLRestServerAuthenticationHttpBasic);
  // - Se permite JWT para peticiones sin sesión
  fRestServer.JWTForUnauthenticatedRequest := TJWTHS256.Create('secret',10,[jrcIssuer,jrcSubject],[]);
  ...
end;
TARIS.LoadServices
var
  lGroupID: TID;
begin
  lGroupID := fRestServer.MainFieldID(TSQLAuthGroupApproach,DEFAULT_GROUP_APP_APPROACH);
  fRestServer.ServiceDefine(TDBService, [IDBService], sicShared, CONTRACT_SERVICE_DB).AllowAllByID([lGroupID]);
  fRestServer.ServiceDefine(TContactDataService, [IContactDataService], sicShared, CONTRACT_SERVICE_CONTACTDATA).AllowAllByID([lGroupID]);
  fRestServer.ServiceDefine(TSchedulerService, [ISchedulerService], sicShared, CONTRACT_SERVICE_SCHEDULER).AllowAllByID([lGroupID]);
  ...
end;I invoke from client side (testing) in the following way:
var
  lResp: SockString;
begin
  lResp :=  TWinHTTP.Post('https://localhost:8898/svc/ContactDataService.UpdateByRecId',
                          '{"aTableName":"TEST01","aRecId":1,"aData":{"PHONE":"1"}}',
                          JSON_CONTENT_TYPE_HEADER+sLineBreak+AuthorizationBearer(
                          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsInBhc3N3b3JkIjoic3lub3BzZSIsImlzcyI6IlRlY25vVm96IFMuQS4iL'+
                          'CJzdWIiOiJBUklTIEpXVCJ9.sYUHk0wIHU-J-Jha5IMyUAaGon-fVKKFjR8oUzcDhSc'));
fVKKFjR8oUzcDhSc'),@lResp);
  ShowMessage(lResp);When is executed, the server return 403 error because Ctxt.Authenticate reject by invalid signature.
Is this correct way ?
What else should I do ?
Thanks.
Esteban
Offline
I would use #13#10 instead of sLineBreak, but it sounds fine.
Please check https://synopse.info/files/html/Synopse … l#TITL_198
(I have just enhanced the documentation about JWT)
Offline
@ab, this not works, I had to do the following to make it work:
Implement the TSQLRestRoutingREST for override Authenticate function:
  /// Clase que implementa una customización del manejo de peticiones REST
  TSQLRestRoutingREST_JWT = class(TSQLRestRoutingREST)
  protected
    // Se incorpora la autenticación con JWT
    function Authenticate: boolean; override;
  end;
function TSQLRestRoutingREST_JWT.Authenticate: boolean;
begin
  Result := inherited Authenticate;
  if (not Result) then
    Result := (Session = CONST_AUTHENTICATION_SESSION_NOT_STARTED) and (Server.JWTForUnauthenticatedRequest<>nil);
  if Result then
      Session := CONST_AUTHENTICATION_NOT_USED; //--> force call Ctxt.AuthenticationCheck(fJWTForUnauthenticatedRequest) in TSQLRestServer.URI
end;and I assigned this TSQLRoutingREST to the REST server:
...
  // 6. Se crea el servidor REST para poder hacer migraciones si es necesario
  // - HandleAuthentication debe ser true para poder manejar usuarios
  fRestServer := TARISRestServer.Create(fModel, fARISDB, fConfig.RESTServer.HandleAuthentication);
  fRestServer.ServicesRouting := TSQLRestRoutingREST_JWT; //--> custom routing rest class
  // - Se permite autenticación/autorización básica (Authorization: Basic ...)
  fRestServer.AuthenticationRegister(TSQLRestServerAuthenticationHttpBasic);
  // - Se permite JWT para peticiones sin sesión
  fRestServer.JWTForUnauthenticatedRequest := TJWTHS256.Create('secret',10,[jrcIssuer,jrcSubject],[]);
...Is this correct this way ? Is there better way ?
Thanks.
Esteban
Offline
Hi @ab, @EMartin
Is this the ultimate solution ? I have a similar requirement, give access to a third party to some interface based services and I will like to use JWT for that.
Couldn't find any example project using JWT and I thought it would be better asking before start coding
Thank you
Offline
I used what I wrote in my last post.
Esteban
Offline
I used what I wrote in my last post.
Thank you Esteban, well I'll have to dig into that.
Offline
Hi,
I have done this to keep session :
Implement a TSQLRestServerAuthenticationHttpBasic :
  TSQLRestServerAuthenticationJWT = class(TSQLRestServerAuthenticationHttpBasic)
  protected
    procedure SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); override;
    function getAlgo(const Value : RawUTF8) : TSignAlgo;
    procedure AuthenticationFailed(Ctxt: TSQLRestServerURIContext; Reason: TNotifyAuthenticationFailedReason);
  public
    function RetrieveSession(Ctxt: TSQLRestServerURIContext): TAuthSession; override;
    function Auth(Ctxt: TSQLRestServerURIContext): boolean; override;
  end;
....
procedure TSQLRestServerAuthenticationJWT.SessionCreate(
  Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser);
var Session: TAuthSession;
    i : Integer;
    Token : RawUTF8;
    jWtClass : TJWTSynSignerAbstractClass;
    vUser, Signat : RawUTF8;
begin
  vUser := User.LogonName;
  inherited SessionCreate(Ctxt, User);
  if Ctxt.Call^.OutStatus = HTTP_SUCCESS then begin
    if fServer.JWTForUnauthenticatedRequest <> nil then begin
      jwtClass := JWT_CLASS[getAlgo(fServer.JWTForUnauthenticatedRequest.Algorithm)];
      (fServer.JWTForUnauthenticatedRequest as jwtClass).SignPrepared.Init(saSha256, SHA256(Ctxt.UserAgent + 'salt'));
      Token := fServer.JWTForUnauthenticatedRequest.Compute(['sessionkey', Ctxt.Call^.OutBody],
                                                             vUser,
                                                             'MyPortail',
                                                             '',0,60, @Signat);
      Ctxt.Call^.OutBody := Token; // <- on retourne le token
    end;
  end;
end;
function TSQLRestServerAuthenticationJWT.getAlgo(
  const Value: RawUTF8): TSignAlgo;
var i : TSignAlgo;
begin
  Result := saSha256;
  for i := low(JWT_TEXT) to High(JWT_TEXT) do
    if SameTextU(Value, JWT_TEXT[i]) then begin
      result := i;
      break;
    end;
end;
procedure TSQLRestServerAuthenticationJWT.AuthenticationFailed(Ctxt: TSQLRestServerURIContext;
  Reason: TNotifyAuthenticationFailedReason);
begin
  if Ctxt is TSQLRestRoutingREST_JWT then
    TSQLRestRoutingREST_JWT(Ctxt).AuthenticationFailed(Reason);
end;
function TSQLRestServerAuthenticationJWT.RetrieveSession(
  Ctxt: TSQLRestServerURIContext): TAuthSession;
var aUserName : RawUTF8;
    User: TSQLAuthUser;
    i : Integer;
    tmpIdsession : Cardinal;
    vSession : variant;
    pSession : PDocVariantData;
    vSessionPrivateSalt : RawUTF8;
begin
  result := inherited RetrieveSession(Ctxt);
  if result <> nil then
    Exit;
  if not Assigned(fServer.JWTForUnauthenticatedRequest) then
    Exit;
  vSessionPrivateSalt := '';
  if Ctxt.AuthenticationBearerToken <> '' then
    if Ctxt.AuthenticationCheck(fServer.JWTForUnauthenticatedRequest) then begin
      aUserName := Ctxt.JWTContent.reg[jrcIssuer];
      User := GetUser(Ctxt,aUserName);
      try
        if User <> nil then begin
          if Ctxt.Server.Sessions <> nil then begin
            if Ctxt.JWTContent.data.GetValueIndex('sessionkey') >= 0 then begin
              vSession := _Json(Ctxt.JWTContent.data.U['sessionkey']);
              if DocVariantType.IsOfType(vSession) then
                vSessionPrivateSalt := vSession.Result;
            end;
            Ctxt.Server.Sessions.Safe.Lock;
            try
              // Search session by privatesalt
              for i := 0 to Pred(Ctxt.Server.Sessions.Count) do
                if SameTextU(vSessionPrivateSalt, TAuthSession(Ctxt.Server.Sessions[i]).ID + '+' + TAuthSession(Ctxt.Server.Sessions[i]).PrivateKey) then begin
                  Result := TAuthSession(Ctxt.Server.Sessions[i]);
                  Ctxt.Session := Result.IDCardinal;
                  break;
                end;
              // Search session for User
              if result = nil then
                for i := 0 to Pred(Ctxt.Server.Sessions.Count) do
                  if TAuthSession(Ctxt.Server.Sessions[i]).User.ID = User.ID then begin
                    Result := TAuthSession(Ctxt.Server.Sessions[i]);
                    Ctxt.Session := Result.IDCardinal;
                    break;
                  end;
            finally
              Ctxt.Server.Sessions.Safe.unLock;
            end;
          end;
        end;
      finally
        User.free;
      end;
    end;
end;
function TSQLRestServerAuthenticationJWT.Auth(
  Ctxt: TSQLRestServerURIContext): boolean;
var aUserName, aPassWord : RawUTF8;
    User: TSQLAuthUser;
begin
  result := False;
  if AuthSessionRelease(Ctxt) then
    exit;
  if not Assigned(fServer.JWTForUnauthenticatedRequest) then begin
      AuthenticationFailed(Ctxt, afJWTRequired);
    Exit;
  end;
  aUserName := Ctxt.InputUTF8OrVoid['UserName'];
  aPassWord := Ctxt.InputUTF8OrVoid['Password'];
  if (aUserName<>'') and (length(aPassWord)>0) then begin
    User := GetUser(Ctxt,aUserName);
    try
      result := User<>nil;
      if result then begin
        if CheckPassword(Ctxt, User, aPassWord) then
          SessionCreate(Ctxt, User)
        else AuthenticationFailed(Ctxt, afInvalidPassword);
      end
      else AuthenticationFailed(Ctxt, afUnknownUser);
    finally
      if result then User.Free;
    end;
  end
  else AuthenticationFailed(Ctxt, afUnknownUser);
end;Implement the TSQLRestRoutingREST for access to protected AuthenticationFailed function :
  TSQLRestRoutingREST_JWT = class(TSQLRestRoutingREST)
  protected
    procedure AuthenticationFailed(Reason: TNotifyAuthenticationFailedReason); override;
  end;
....
procedure TSQLRestRoutingREST_JWT.AuthenticationFailed(
  Reason: TNotifyAuthenticationFailedReason);
begin
  inherited AuthenticationFailed(Reason);
end;For the Rest server creation :
   fRestServer.AuthenticationRegister(TSQLRestServerAuthenticationJWT);
   fRestServer.ServicesRouting := TSQLRestRoutingREST_JWT;
   fRestServer.JWTForUnauthenticatedRequest := fServerSettings.AuthenticationMode.JWTClass.Create('secret', 0, [jrcIssuer, jrcSubject], [], 60);JWTForUnauthenticatedRequest instance is used in TSQLRestServerAuthenticationJWT.
Offline
Thank you @Chris75018 I'm still busy making new services and once I finish I'll take a deep dive into this, maybe (just maybe) you will have me here asking more questions 
 .
Offline
Hi,
I have made modifications for use TSQLHttpClient with this authentication.
Here the complete Unit :
unit Web.Serveur.U_JWT;
interface
uses
  Windows,
  SysUtils,
  Classes,
  SynZip,
  SynLZ,
  SynCrtSock,
  {$ifndef NOHTTPCLIENTWEBSOCKETS}
  SynBidirSock, // for WebSockets
  {$endif}
  SynCrypto,    // for hcSynShaAes
  SynCommons,
  SynLog,
  mORMot,
  mORMotHttpClient;
type
  TSQLRestRoutingREST_JWT = class(TSQLRestRoutingREST)
  protected
    procedure AuthenticationFailed(Reason: TNotifyAuthenticationFailedReason); override;
  end;
  TSQLRestServerAuthenticationJWT = class(TSQLRestServerAuthenticationHttpBasic)
  protected
    function CheckPassword(Ctxt: TSQLRestServerURIContext;
      User: TSQLAuthUser; const aPassWord: RawUTF8): boolean; override;
    procedure SessionCreate(Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser); override;
    function getAlgo(const Value : RawUTF8) : TSignAlgo;
    procedure AuthenticationFailed(Ctxt: TSQLRestServerURIContext; Reason: TNotifyAuthenticationFailedReason);
    class function ClientGetSessionKey(Sender: TSQLRestClientURI;
      User: TSQLAuthUser; const aNameValueParameters: array of const): RawUTF8; override;
  public
    function RetrieveSession(Ctxt: TSQLRestServerURIContext): TAuthSession; override;
    function Auth(Ctxt: TSQLRestServerURIContext): boolean; override;
    class function ClientSetUser(Sender: TSQLRestClientURI; const aUserName, aPassword: RawUTF8;
      aPasswordKind: TSQLRestServerAuthenticationClientSetUserPassword=passClear;
      const aHashSalt: RawUTF8=''; aHashRound: integer=20000): boolean; override;
  end;
  TSQLHttpClientJWT = class(TSQLHttpClientRequest)
  private
    fJWT: RawUTF8;
  protected
    procedure InternalSetClass; override;
    function InternalRequest(const url, method: RawUTF8;
      var Header, Data, DataType: RawUTF8): Int64Rec; override;
  public
    function SetUser(const aUserName, aPassword: RawUTF8;
      aHashedPassword: Boolean=false): boolean; reintroduce;
    property jwt : RawUTF8 read fJWT write fJWT;
  end;
implementation
function HeaderOnce(const Head : RawUTF8; upper: PAnsiChar): RawUTF8;
  {$ifdef HASINLINE}inline;{$endif}
begin
  if (Head <> '') then
    result := FindIniNameValue(pointer(Head),upper)
  else result := '';
end;
{ TSQLRestRoutingREST_JWT }
procedure TSQLRestRoutingREST_JWT.AuthenticationFailed(
  Reason: TNotifyAuthenticationFailedReason);
begin
  inherited AuthenticationFailed(Reason);
end;
{ TSQLRestServerAuthenticationJWT }
function TSQLRestServerAuthenticationJWT.Auth(
  Ctxt: TSQLRestServerURIContext): boolean;
var aUserName, aPassWord : RawUTF8;
    User: TSQLAuthUser;
begin
  result := False;
  if AuthSessionRelease(Ctxt) then
    exit;
  if not Assigned(fServer.JWTForUnauthenticatedRequest) then begin
      AuthenticationFailed(Ctxt, afJWTRequired);
    Exit;
  end;
  aUserName := Ctxt.InputUTF8OrVoid['UserName'];
  aPassWord := Ctxt.InputUTF8OrVoid['Password'];
  if (aUserName<>'') and (length(aPassWord)>0) then begin
    User := GetUser(Ctxt,aUserName);
    try
      result := User<>nil;
      if result then begin
        if CheckPassword(Ctxt, User, aPassWord) then
          SessionCreate(Ctxt, User)
        else AuthenticationFailed(Ctxt, afInvalidPassword);
      end
      else AuthenticationFailed(Ctxt, afUnknownUser);
    finally
      if result then User.Free;
    end;
  end
  else AuthenticationFailed(Ctxt, afUnknownUser);
end;
procedure TSQLRestServerAuthenticationJWT.AuthenticationFailed(Ctxt: TSQLRestServerURIContext;
  Reason: TNotifyAuthenticationFailedReason);
begin
  if Ctxt is TSQLRestRoutingREST_JWT then
    TSQLRestRoutingREST_JWT(Ctxt).AuthenticationFailed(Reason);
end;
function TSQLRestServerAuthenticationJWT.CheckPassword(
  Ctxt: TSQLRestServerURIContext; User: TSQLAuthUser;
  const aPassWord: RawUTF8): boolean;
var expectedPass: RawUTF8;
begin
  expectedPass := User.PasswordHashHexa;
  User.PasswordPlain := aPassWord;
  User.PasswordHashHexa := SHA256(aPassWord);
  result := IdemPropNameU(User.PasswordHashHexa,expectedPass);
end;
class function TSQLRestServerAuthenticationJWT.ClientGetSessionKey(
  Sender: TSQLRestClientURI; User: TSQLAuthUser;
  const aNameValueParameters: array of const): RawUTF8;
var resp: RawUTF8;
    values: array[0..9] of TValuePUTF8Char;
    a: integer;
    algo: TSQLRestServerAuthenticationSignedURIAlgo absolute a;
begin
  Result := '';
  if (Sender.CallBackGet('Auth',aNameValueParameters,resp)=HTTP_SUCCESS) then
    result := resp;
end;
class function TSQLRestServerAuthenticationJWT.ClientSetUser(
  Sender: TSQLRestClientURI; const aUserName, aPassword: RawUTF8;
  aPasswordKind: TSQLRestServerAuthenticationClientSetUserPassword;
  const aHashSalt: RawUTF8; aHashRound: integer): boolean;
var res: RawUTF8;
    U: TSQLAuthUser;
    vTmp : Variant;
begin
  result := false;
  if (aUserName='') or (Sender=nil) then
    exit;
  if not Sender.InheritsFrom(TSQLHttpClientJWT) then
    exit;
  if aPasswordKind<>passClear then
    raise ESecurityException.CreateUTF8('%.ClientSetUser(%) expects passClear',
      [self,Sender]);
  Sender.SessionClose; // ensure Sender.SessionUser=nil
  try // inherited ClientSetUser() won't fit with Auth() method below
    ClientSetUserHttpOnly(Sender,aUserName,aPassword);
    TSQLHttpClientJWT(Sender).jwt := '';
    U := TSQLAuthUser(Sender.Model.GetTableInherited(TSQLAuthUser).Create);
    try
      U.LogonName := trim(aUserName);
      res := ClientGetSessionKey(Sender,U,['Username', aUserName, 'password', aPassword]);
      if res<>'' then begin
        vTmp := _JsonFast(res);
        if DocVariantType.IsOfType(vTmp) then begin
          result := TSQLHttpClientJWT(Sender).SessionCreate(self,U,TDocvariantData(vTmp).U['result']);
          if result then
            TSQLHttpClientJWT(Sender).jwt := TDocvariantData(vTmp).U['jwt'];
        end;
      end;
    finally
      U.Free;
    end;
  finally
    if not result then begin
      // on error, reverse all values
      TSQLHttpClientJWT(Sender).jwt := '';
    end;
    if Assigned(Sender.OnSetUser) then
      Sender.OnSetUser(Sender); // always notify of user change, even if failed
  end;
end;
function TSQLRestServerAuthenticationJWT.getAlgo(
  const Value: RawUTF8): TSignAlgo;
var i : TSignAlgo;
begin
  Result := saSha256;
  for i := low(JWT_TEXT) to High(JWT_TEXT) do
    if SameTextU(Value, JWT_TEXT[i]) then begin
      result := i;
      break;
    end;
end;
function TSQLRestServerAuthenticationJWT.RetrieveSession(
  Ctxt: TSQLRestServerURIContext): TAuthSession;
var aUserName : RawUTF8;
    User: TSQLAuthUser;
    i : Integer;
    tmpIdsession : Cardinal;
    vSession : variant;
    pSession : PDocVariantData;
    vSessionPrivateSalt : RawUTF8;
begin
  result := inherited RetrieveSession(Ctxt);
  if result <> nil then
    Exit;
  if not Assigned(fServer.JWTForUnauthenticatedRequest) then
    Exit;
  vSessionPrivateSalt := '';
  if Ctxt.AuthenticationBearerToken <> '' then
    if Ctxt.AuthenticationCheck(fServer.JWTForUnauthenticatedRequest) then begin
      aUserName := Ctxt.JWTContent.reg[jrcIssuer];
      User := GetUser(Ctxt,aUserName);
      try
        if User <> nil then begin
          if Ctxt.Server.Sessions <> nil then begin
            if Ctxt.JWTContent.data.GetValueIndex('sessionkey') >= 0 then begin
              vSession := _Json(Ctxt.JWTContent.data.U['sessionkey']);
              if DocVariantType.IsOfType(vSession) then
                vSessionPrivateSalt := vSession.Result;
            end;
            Ctxt.Server.Sessions.Safe.Lock;
            try
              // Search session for User
              if (reOneSessionPerUser in Ctxt.Call^.RestAccessRights^.AllowRemoteExecute) and
                (Ctxt.Server.Sessions<>nil) then
                for i := 0 to Pred(Ctxt.Server.Sessions.Count) do
                  if TAuthSession(Ctxt.Server.Sessions[i]).User.ID = User.ID then begin
                    Result := TAuthSession(Ctxt.Server.Sessions[i]);
                    Ctxt.Session := Result.IDCardinal;
                    break;
                  end;
              // Search session by privatesalt
              if result = nil then
                for i := 0 to Pred(Ctxt.Server.Sessions.Count) do
                  if SameTextU(vSessionPrivateSalt, TAuthSession(Ctxt.Server.Sessions[i]).ID + '+' + TAuthSession(Ctxt.Server.Sessions[i]).PrivateKey) then begin
                    Result := TAuthSession(Ctxt.Server.Sessions[i]);
                    Ctxt.Session := Result.IDCardinal;
                    break;
                  end;
            finally
              Ctxt.Server.Sessions.Safe.unLock;
            end;
          end;
        end;
      finally
        User.free;
      end;
    end;
end;
procedure TSQLRestServerAuthenticationJWT.SessionCreate(
  Ctxt: TSQLRestServerURIContext; var User: TSQLAuthUser);
var Session: TAuthSession;
    i : Integer;
    Token : RawUTF8;
    jWtClass : TJWTSynSignerAbstractClass;
    vPass, vUser, Signat, vSessionKey : RawUTF8;
    vTmp : Variant;
begin
  vUser := User.LogonName;
  vPass := User.PasswordHashHexa;
  inherited SessionCreate(Ctxt, User);
  if Ctxt.Call^.OutStatus = HTTP_SUCCESS then begin
    if fServer.JWTForUnauthenticatedRequest <> nil then begin
      jwtClass := JWT_CLASS[getAlgo(fServer.JWTForUnauthenticatedRequest.Algorithm)];
      Token := (fServer.JWTForUnauthenticatedRequest as jwtClass).Compute([
                                                                           'sessionkey', Ctxt.Call^.OutBody],
                                                                           vUser,
                                                                           'jwt.access',
                                                                           '',0,60, @Signat);
      vTmp := _JsonFast(Ctxt.Call^.OutBody);
      if DocVariantType.IsOfType(vTmp) then
        Ctxt.Call^.OutBody := _Obj(['result', TDocvariantData(vTmp).U['result'], 'jwt', Token]);
    end;
  end;
end;
{ TSQLHttpClientJWT }
function TSQLHttpClientJWT.InternalRequest(const url, method: RawUTF8;
  var Header, Data, DataType: RawUTF8): Int64Rec;
var vBasic : RawUTF8;
    h : Integer;
begin
  if fjwt <> '' then begin // Change Header if jwt exist
    vBasic := HeaderOnce(Header, 'AUTHORIZATION: BASIC ');
    if vBasic <> '' then begin
      h := PosEx(vBasic, Header);
      if h = 22 then
        header := copy(Header, h + Length(vBasic), Length(header))
      else header := copy(Header, 1, h - 21) + copy(Header, h + Length(vBasic), Length(header));
      header := Trim(header);
    end;
    Header := trim(HEADER_BEARER_UPPER + fJWT + #13#10 + Header);
  end;
  result := inherited InternalRequest(url, method, Header, Data, DataType);
end;
procedure TSQLHttpClientJWT.InternalSetClass;
begin
  fRequestClass := TWinHTTP;
  inherited;
end;
function TSQLHttpClientJWT.SetUser(const aUserName, aPassword: RawUTF8;
  aHashedPassword: Boolean): boolean;
const HASH: array[boolean] of TSQLRestServerAuthenticationClientSetUserPassword =
  (passClear, passHashed);
begin
  if self=nil then begin
    result := false;
    exit;
  end;
  result := TSQLRestServerAuthenticationJWT.
    ClientSetUser(self,aUserName,aPassword,HASH[aHashedPassword]);
end;
end.TSQLRestServerAuthenticationJWT.Auth() method return a RawUTF8 JSON Object containing two fields :
- result : session privatesalt (ID + '+' + privatekey) 
- jwt :  the Token
The TSQLHttpClientJWT class is to used with Delphi client.
FClient := TSQLHttpClientJWT.Create(SERVER_ADDRESS, DEFAULT_PORT, FDBModel);
if FClient.SetUser('synopse', 'mormot', False) then
....For Ajax client, you must parse Auth() API response.
$("#submit").click(function(){
 
 var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(event) {
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) {
            tmp = JSON.parse(xhr.responseText);
            sessionStorage.setItem("jwt",tmp.jwt);
        } else {
            tmp = xhr.statusText;
        }
      }	  
    };
    username = $("#login").val();
    mpass = $("#mdp").val();
	
    xhr.open('GET', "http://127.0.0.1/MyServer/Auth?username=" + username + "&password=" + mpass, true);
    xhr.send(null);    
});And use jwt value in each call :
var GET_Testjwt = function(jwt,callback){
  var xhr = new XMLHttpRequest();
   xhr.onreadystatechange = function(event) {
      if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) {
            response = xhr.responseText;
        } else {
            response = xhr.statusText;
        }
       callback(200, response);
      }	  
    };
  jwt = sessionStorage.getItem("jwt");
  xhr.open('GET', "http://127.0.0.1/MyServer/" + arequest, true);
  xhr.setRequestHeader('Authorization', ' bearer ' + jwt);
  xhr.send(null);    
}I hope I did not forget anything.
The code runs on the tests and will soon be in production. I'll see if it's okay.
Any comments or suggestions are welcome.
Offline
Thanks for sharing!
Please don't post such huge piece of code directly in the forum.
Check the https://synopse.info/forum/misc.php?action=rules
Offline
Sorry Ab. I'll be careful next time.
Offline
@Chris75018 Great! Thank you
Offline
This is great contribution, something that's really needed when dealing with external services.
@ab it would be great if integrated into main codebase.
Offline
Hi,
For those who are interested here is the latest version of this piece of code.
https://1drv.ms/u/s!Ajd1--MjHzRWqw7-27x … i?e=Oz6DWO
I will try to make a sample project quickly.
AB, if you can watch and tell me if I have not done a lot of bullshit, that would help me a lot. 
Thank you.
Offline
You can find a sample server here : 
https://1drv.ms/u/s!Ajd1--MjHzRWqxG2SeS … N?e=1JwpMd
I'm using an overloaded version of TSQLRestDBServer to add two APIs:
- IsValidToken () to test whether the token is still active and allowed.
- RefreshToken () to regenerate it if it has expired. If the session is no longer existing, this method adds a new session.
  RefreshToken expects two parameters (User and password).  
These methods must be called with a bearer in the http header.
Compiled with Delphi XE3.
Last edited by Chris75018 (2019-08-16 07:58:01)
Offline
Hi @Chris75018
I just downloaded your demo to give it a quick glance, and although I have XE3 on my personal computer, for this project I must use XE and sadly this :
JwtHelperForlAuthenticationMode = record helper for lAuthenticationModeSeems not supported on XE, I will try later with XE3 but I must find out how to replace that code to make it work on XE, either way, it's very nice of you to share this code with us, thank you.
Offline
Hi @moctes
i have made the correction.
You can download the sample project here
https://1drv.ms/u/s!Ajd1--MjHzRWqxG2SeS … N?e=1JwpMd
PS : I don't have XE to validate the compilation.
Offline
@Chris75018 I couldn't find the time to check the updated demo until today, works like a Charm !! We are still pretty busy devoloping the services and testing them without authentication, I hope to be trying out with authentication very soon, your sample and @Emartin's code are extremely useful to me.
Offline
Pages: 1