#51 2016-02-25 12:11:01

mpv
Member
From: Ukraine
Registered: 2012-03-24
Posts: 1,571
Website

Re: Javascript authentication

@albanirneves
Why you do not use Ext.Ajax.request instead of browser-specific XMLHttpRequest?

Offline

#52 2017-08-21 07:47:11

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

Hello Guys, i followed the code from RangerX and esmondb to implement the Authentication in typescript (for angular 4).

The login works as expected, and i get a signature, but i fail at the Session signature, the Server always answer wtih 403 : Forbidden

Code for the Authentication

public login(username, password): void {
    this.Username = username;
    this.PasswordHashHexa = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash("salt" + password)); //Password Hex Sha256 generate
    this.http.get('http://localhost:8080/' + this.ServerAuthRoot + '/auth?UserName=' + username).map(this.extractData).catch(this.handleErrorObservable).subscribe(
      response => {// We got a Nonce from the Server in response.result
        this.gotNonce(response.result);
      },
      error => {// Some Error occured, we did not got a Nonce from the Server
        alert(JSON.stringify(error));
      }
    );
  }



  private gotNonce(aNonce: string): void {
    var shaPass: string;

    var aClientNonce = "", s = "", d = new Date();

    aClientNonce = d.getFullYear().toString();
    s = d.getMonth().toString();

    if (s.length === 1) {
      s = '0' + s;
    }

    aClientNonce = aClientNonce + '-' + s;
    s = d.getDate().toString();

    if (s.length === 1) {
      s = '0' + s;
    }

    aClientNonce = aClientNonce + '-' + s + ' ';
    s = d.getHours().toString();

    if (s.length === 1) {
      s = '0' + s;
    }

    aClientNonce = aClientNonce + s;
    s = d.getMinutes().toString();

    if (s.length === 1) {
      s = '0' + s;
    }

    aClientNonce = aClientNonce + ':' + s;
    s = d.getSeconds().toString();

    if (s.length === 1) {
      s = '0' + s;
    }

    aClientNonce = aClientNonce + ':' + s;
    aClientNonce = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(aClientNonce))

    shaPass = sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(this.ServerAuthRoot + aNonce + aClientNonce + this.Username + this.PasswordHashHexa));
    s = 'http://localhost:8080/' + this.ServerAuthRoot + '/auth?UserName=' + this.Username + '&Password=' + shaPass + '&ClientNonce=' + aClientNonce;
    // prompt('Resulting Call :', s);
    this.http.get(s).map(this.extractData).catch(this.handleErrorObservable).subscribe(
      response => {
        alert(JSON.stringify(response));
        this.gotSession(response.result);
      },
      error => {
        alert(JSON.stringify(error));
      }
    )
  }

  private gotSession(aSessionKey: string) {
    var i: number = aSessionKey.indexOf("+");
    this.SessionID = parseInt(aSessionKey.slice(0, i));
    this.SessionIDHexa8 = this.SessionID.toString(16);

    while (this.SessionIDHexa8.length < 8) {
      this.SessionIDHexa8 = '0' + this.SessionIDHexa8;
    }

    this.SessionPrivateKey = this.crc32(this.PasswordHashHexa, this.crc32(aSessionKey, 0));
  };

and the Session Signature

public SessionSign(url: string): string {
    var Tix: number;
    var Nonce: string;
    var s: any;
    var ss: any;
    var d: Date = new Date();

    Tix = d.getTime();

    if (this.LastSessionTickCount == Tix) {
      Tix = Tix + 1;
    }
    
    this.LastSessionTickCount = Tix;
    Nonce = Tix.toString(16);
    

    while (Nonce.length < 8) {
      Nonce = '0' + Nonce;
    }

    if (Nonce.length > 8) {
      Nonce = Nonce.slice(Nonce.length - 8, Nonce.length);
    }
    
    ss = this.crc32(url, this.crc32(Nonce, this.SessionPrivateKey)).toString(16);
    
    while (ss.length < 8) {
      ss = '0' + ss;
    }
    
    s = url.indexOf("?") == -1 ? url + '?session_signature=' : url + '&session_signature=';

    return s + this.SessionIDHexa8 + Nonce + ss;
  }

and the crc32 function

private Crc32Tab = [ /* CRC polynomial 0xEDB88320 */
    0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,
    0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
    0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,
    0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
    0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,
    0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
    0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,
    0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
    0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,
    0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
    0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,
    0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
    0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,
    0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
    0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,
    0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
    0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,
    0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
    0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,
    0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
    0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,
    0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
    0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,
    0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
    0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,
    0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
    0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,
    0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
    0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,
    0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
    0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,
    0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D];

    private Crc32Add(crc,c)
    /*
      'crc' should be initialized to 0xFFFFFFFF and after the computation it should be
      complemented (inverted).

      CRC-32 is also known as FCS-32.

      If the FCS-32 is calculated over the data and over the complemented FCS-32, the
      result will always be 0xDEBB20E3 (without the complementation).
    */
    {
      return this.Crc32Tab[(crc^c)&0xFF]^((crc>>8)&0xFFFFFF);
    }

    private crc32(str, crc)
    {
      var n, len;
      len = str.length;
      //var crc;
      if (typeof(crc) == "undefined") { crc = 0xFFFFFFFF; }
      else {
        crc = crc^0xFFFFFFFF; //crc = ~crc;
        if (crc < 0) {
          crc = 4294967296 + crc;
        } 
      }
      //crc=0xFFFFFFFF;
      for (n=0; n<len; n = n+1) {
        crc = this.Crc32Add(crc,str.charCodeAt(n));
      }
      crc = crc^0xFFFFFFFF; //crc = ~crc;
      if (crc < 0) {
        crc = 4294967296 + crc;
      } 
      return crc;//^0xFFFFFFFF;
    }
}

I don't know why the Session Signature doesnt work as expected.

I hope you guy's can help me out.

Edit : And Sorry for reviving an old Post

Last edited by ImproSnake (2017-08-21 09:01:31)

Offline

#53 2017-08-21 09:46:17

MagoSchmidt
Member
Registered: 2017-07-21
Posts: 5

Re: Javascript authentication

I think this is not related with javascript authentication, but with server side authentication. When I started using javascript authentication I faced similar problems until really understand how authentication at server side works. I use "interface based" mode and faced this problem when implementing OnAuthenticationUserRetrieve....

Last edited by MagoSchmidt (2017-08-21 09:47:51)

Offline

#54 2017-08-21 09:57:23

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

I don't now why this should be an issue at all.

This is all i do on the server side

restModel := TSQLModel.Create([], ROOT_NAME);
  try
    // initialize Rest-Server
    restServer := TSQLRestServerFullMemory.Create(restModel,false);
    restServer.CreateMissingTables(0, [itoNoAutoCreateUsers]);
    restServer.AuthenticationRegister(TSQLRestServerAuthenticationDefault).Options := [];
    fillAuthFromDB(restServer, dbConProbs);
    try
      // register the Services on the Server
      restServer.ServiceDefine(TServiceHello, [IHello], sicPerSession);

      // launch HttpServer which publish the restServer
      // useHttpApiRegisteringURI = AutoRegistering the URI specified by the restServer + PORT_NAME
      httpServer := TSQLHttpServer.Create(PORT_NAME, [restServer], '+', useHttpApiRegisteringURI);
      try
        httpServer.AccessControlAllowOrigin := '*';
        writeln(#10'Background server is running.'#10);
        writeln('Press [Enter] to close the server.'#10);
        readln;
      finally
        httpServer.Free;
      end;
    finally
      restServer.Free;
    end;
  finally
    restModel.Free;
  end;

And the implementation of the services + Add the Users from my db to the sqlite tables to use the mormot framework auth of course.

And the session signature works as expected with a mormot client. There i can use the services after login, That's why i assume something is wrong in my javascript code

Last edited by ImproSnake (2017-08-21 09:58:52)

Offline

#55 2017-08-21 10:01:42

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

You disabled authentication on the server side.
So it is logical to be able to create sessions without any authentication...

Offline

#56 2017-08-21 10:03:27

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

ab wrote:

You disabled authentication on the server side.
So it is logical to be able to create sessions without any authentication...

Where i disabled the Authentication, i thought the Default Authentication is the Safe Authentication mode ???

Offline

#57 2017-08-21 10:09:19

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Try to call AuthenticationRegister BEFORE CreateMIssingTables.

Offline

#58 2017-08-21 10:15:42

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

@ab

Thank you for your help. I call the AuthenticationRegister now before i call CreateMissingTables. But i still get 403 Forbidden from the Webapplication but all work as expected wiht the mormot Client.

I can login successfully and get a Session but i cant get the signature to work or whatever goes wrong there.

after login

{"result":"1169656096+29EBAE633FDCDA4F25C690589C8CE3863A46063CBDBE9D969C790FA9EF93B64F","logonid":4,"logonname":"***********","logondisplay":"","logongroup":3,"timeout":60,"server":"Server","version":""}

And now after call a service

Response with status: 403 Forbidden for URL: http://localhost:8080/API/Hello.Hello?session_signature=45b789200450f02caf6a4349

{
"errorCode":403,
"errorText":"Authentication Failed: Invalid signature (0)"
}

I hope u can help me further.

Thank you

Last edited by ImproSnake (2017-08-21 10:24:03)

Offline

#59 2017-08-21 14:44:06

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

The signature seems incorrectly computed.

Offline

#60 2017-08-22 05:20:56

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

Hi ab, thank you again for your help.

I lack the experience to see that the signature is wrong by just looking at it big_smile

Thats how i compute the signature.

public SessionSign(url: string): string {
    var Tix: number;
    var Nonce: string;
    var s: any;
    var ss: any;
    var d: Date = new Date();

    Tix = d.getTime();

    if (this.LastSessionTickCount == Tix) {
      Tix = Tix + 1;
    }
    
    this.LastSessionTickCount = Tix;
    Nonce = Tix.toString(16);

    while (Nonce.length < 8) {
      Nonce = '0' + Nonce;
    }

    if (Nonce.length > 8) {
      Nonce = Nonce.slice(Nonce.length - 8, Nonce.length);
    }
    
    ss = this.crc32(url, this.crc32(Nonce, this.SessionPrivateKey)).toString(16);
    
    while (ss.length < 8) {
      ss = '0' + ss;
    }
    
    s = url.indexOf("?") == -1 ? url + '?session_signature=' : url + '&session_signature=';

    return s + this.SessionIDHexa8 + Nonce + ss;
  }

Where SessionIdJHexa8 and SessionPrivateKey  are

 private gotSession(aSessionKey: string) {
    var i: number = aSessionKey.indexOf("+");
    this.SessionID = parseInt(aSessionKey.slice(0, i));
    this.SessionIDHexa8 = this.SessionID.toString(16);

    while (this.SessionIDHexa8.length < 8) {
      this.SessionIDHexa8 = '0' + this.SessionIDHexa8;
    }

    this.SessionPrivateKey = this.crc32(this.PasswordHashHexa, this.crc32(aSessionKey, 0));
  };

Last edited by ImproSnake (2017-08-22 05:22:05)

Offline

#61 2017-08-22 06:30:32

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Try to debug on the server side, why is the session signature rejected.

Offline

#62 2017-08-22 10:04:28

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

Hi again. I worked on the server side debug.

First the mormot Log to conolse prints out something like  "Invalid Signature expected xxxxxx got yyyyy "

And on the server it comes down to mormot.pas line 51804

if HexDisplayToCardinal(PTimeStamp+8,aSignature) and
       (aSignature=aExpectedSignature) then

Obviously the Expected Signature and the aSignature doesnt match. But i cant find out why...

I would be glad if someone can help here

Last edited by ImproSnake (2017-08-22 10:16:44)

Offline

#63 2017-08-22 14:57:09

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Debug on both side, of course, in synch.
And try to find out what is wrong with your client signature.

Offline

#64 2017-08-23 06:56:09

esmondb
Member
From: London
Registered: 2010-07-20
Posts: 299

Re: Javascript authentication

I think there was a bug in the original code. in the crc32 function try changing this line:

 crc = crc ^ (-1);

to this:

 crc = crc^0xFFFFFFFF; 

Offline

#65 2017-08-23 07:24:52

ImproSnake
Member
Registered: 2017-06-20
Posts: 30

Re: Javascript authentication

Thank you all for your help.

The error was much much easier than the crc - Function and so on.

I just missused the Session Sign Function. I set the whole url and not just root + params.

Instead of : 'http//:xxxxxxxx/ServerRoot/Service'+Params

You have to set 'ServerRoot/Service'+params

as URL for the SessionSign Function.

Just in case somone got stuck on the same Problem.

Sorry for wasting your time.

Last edited by ImproSnake (2017-08-23 07:28:07)

Offline

#66 2017-10-12 08:29:36

tigerbeer
Member
Registered: 2017-09-29
Posts: 1

Re: Javascript authentication

@albanirneves
i am a newbie of Extjs,could you pl tell me how can i add or use your Ext.js.Auth to example 18. thinks a lot.

Offline

#67 2017-12-11 15:23:37

pandaben7890
Member
Registered: 2017-12-11
Posts: 12

Re: Javascript authentication

Hello everyone, I try searching everywhere but nothing solve my problem.

I can login and everything works fine. Except that once in a while when client(angular2) call API to mormot server, it will throw error on chrome-debug with
{errorCode:403, errorText:"Authentication Failed: Invalid signature (0)"}

Likely to happen more frequent when server has many access at that time.

This is my SessionSign method on angular2

private SessionSign(url) {
	var Tix, Nonce, s, ss, d = new Date();
	Tix = d.getTime();
	if (this.fLastSessionTickCount == Tix) {Tix = Tix + 1;}
	this.fLastSessionTickCount = Tix;
	Nonce = Tix.toString(16);
	while(Nonce.length < 8) { Nonce = '0'+Nonce; }
	if (Nonce.length > 8) { Nonce = Nonce.slice(Nonce.length-8, Nonce.length) }
	ss = crc32(url, crc32(Nonce, this.fSessionPrivateKey)).toString(16);
	while(ss.length < 8) { ss = '0'+ss; }
	s = url.indexOf("?") == -1 ? url+'?session_signature=' : url+'&session_signature=';
	return s + this.fSessionIDHexa8 + Nonce + ss;
}

This is when I start service on Server

function TSM.StartService(port, databasename: String): Boolean;
begin
	aProps := TOleDBMSSQL2012ConnectionProperties.Create(servername, databasename, username, password);
	VirtualTableExternalRegister(Model, TSQLGroups, aProps, 'dbo.LOT_GROUPS');
	VirtualTableExternalRegister(Model, TSQLUsers,  aProps, 'dbo.LOT_USERS');
	
	ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True);
	ServerDB.CreateMissingTables(0);
	ServerDB.ServiceRegister(TServiceDatabase, [TypeInfo(IDatabase)], sicShared);
	ServerDB.ServiceRegister(TServiceDatabase2, [TypeInfo(IDatabase2)], sicShared);
	
	HttpServer := TSQLHttpServer.Create(port, [ServerDB]);
	HttpServer.AccessControlAllowOrigin := '*';
	
	Result := True;
end;

Called method

function AuditLog_GetAuditLog(dataobj: RawUTF8): TServiceCustomAnswer;
var res: ISQLDBRows;
    sql,data,lotstatus,onlyunread: RawUTF8;
    watch,size: Integer;
    obj: Variant;
    showAll: Boolean;
begin
	obj := _JSON(dataobj);
	size := 1000;
	showAll := False;
	if obj.Exists('size') then begin
		size := obj.size;
	end;
	if obj.Exists('showAll') and (obj.showAll='Y') then
		showAll := True;

	//---- build SQL command ----
	sql := 'select TOP '+size.ToString+' * from AuditLog';
	if not showAll then
		sql := sql + Format(' where (UserID=%s or ChangeUserID=%s)', [SQLB.Str(obj.userid), SQLB.Str(obj.userid)]);
	sql := sql + ' order by ID';
	res := SM.aProps.ExecuteInlined(sql, True);
	
	Result.Content := Data_Content(res, 'false'); // make json
	Result.Header  := 'Content-type: application/json';
end;

Last edited by pandaben7890 (2017-12-11 15:37:40)

Offline

#68 2017-12-11 15:37:11

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Could you try to debug on the server side, in TSQLRestServer, and find out why exactly the signature is rejected?

Look in TSQLRestServerAuthenticationSignedURI.RetrieveSession method.

Offline

#69 2017-12-12 10:46:46

pandaben7890
Member
Registered: 2017-12-11
Posts: 12

Re: Javascript authentication

Trying to debug but don't know what i'm doing wrong. Put breakpoint but never stop here U_U.

ss.PNG

Offline

#70 2017-12-12 10:55:40

emaxx
Member
Registered: 2014-07-03
Posts: 18

Re: Javascript authentication

consider using NoTimeStampCoherencyCheck, see https://synopse.info/forum/viewtopic.php?id=1983

Offline

#71 2017-12-12 10:59:01

pandaben7890
Member
Registered: 2017-12-11
Posts: 12

Re: Javascript authentication

@emaxx

Try adding but error still happen

function TSM.StartService(port, databasename: String): Boolean;
begin
	aProps := TOleDBMSSQL2012ConnectionProperties.Create(servername, databasename, username, password);
	VirtualTableExternalRegister(Model, TSQLGroups, aProps, 'dbo.LOT_GROUPS');
	VirtualTableExternalRegister(Model, TSQLUsers,  aProps, 'dbo.LOT_USERS');
	
	ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True);
	ServerDB.CreateMissingTables(0);
	ServerDB.ServiceRegister(TServiceDatabase, [TypeInfo(IDatabase)], sicShared);
	ServerDB.ServiceRegister(TServiceDatabase2, [TypeInfo(IDatabase2)], sicShared);
	(ServerDB.AuthenticationRegister(TSQLRestServerAuthenticationDefault) as TSQLRestServerAuthenticationSignedURI).NoTimeStampCoherencyCheck := true;
	
	HttpServer := TSQLHttpServer.Create(port, [ServerDB]);
	HttpServer.AccessControlAllowOrigin := '*';
	
	Result := True;
end;

Offline

#72 2017-12-12 11:58:25

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

So try to debug sooner in TSQLRestServer.URI for instance.

Offline

#73 2017-12-12 12:27:32

pandaben7890
Member
Registered: 2017-12-11
Posts: 12

Re: Javascript authentication

@ab
Not work either.
Try put breakpoint on ServerDB := TSQLRestServerWS.Create(Model, ':memory:', True); not break here too.

Did I do something wrong about debugging?

Offline

#74 2017-12-12 14:48:13

esmondb
Member
From: London
Registered: 2010-07-20
Posts: 299

Re: Javascript authentication

It could be a missing '/'

Offline

#75 2017-12-19 23:07:48

Kixemi
Member
Registered: 2017-02-10
Posts: 10

Re: Javascript authentication

Hello I am a newbie, but I used a Javascript from somewhere here, perhaps it was SynAuth.js, for the Login and "create user Session" ... and i sometimes got "Invalid signature" on the call right after. In the end it appeared that the signature was computed on the Javascripts side ! BEFORE ! the return of a valid SessionKey was returned. So the signature was computed on a nonexistent Sessionkey. This is just a thought, but perhaps there is something in it! wink

Offline

#76 2017-12-20 13:25:15

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Sounds like a crc32 computation problem, maybe due to how weird JavaScript handle integer arithmetic (a missing >>>0 operation?).

Offline

#77 2018-04-16 14:30:51

polidados
Member
From: Brazil
Registered: 2017-03-08
Posts: 14

Re: Javascript authentication

Hello, I would like to know how I can store the session key with javascript for use in future requests on a website.

Offline

#78 2018-04-16 14:36:37

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

For security reasons, the session will expire so you will need to create a new session (and a session key).

Offline

#79 2018-04-16 14:50:47

polidados
Member
From: Brazil
Registered: 2017-03-08
Posts: 14

Re: Javascript authentication

Thanks ab for the info.

Last edited by polidados (2018-04-16 14:51:01)

Offline

#80 2018-12-14 18:19:16

imperyal
Member
Registered: 2018-10-11
Posts: 54

Re: Javascript authentication

Here it is yet another implementation based on RangerX's code. I implemented it as a javascript class. It seems to be working just fine.

GitHub repository: https://github.com/imperyal/synopse-login


/**********************************************************************************************/
/*                                                                                            */
/*    ======= Synopse login class =======                                                     */
/*                                                                                            */
/*                                                                                            */
/* - Based on RangerX's code ( https://synopse.info/forum/viewtopic.php?pid=2995#p2995 )      */
/* - Requires JQuery                                                                          */
/* - Requires sha256 (https://github.com/emn178/js-sha256)                                    */
/* - crc32 code included (from https://stackoverflow.com/questions/18638900/javascript-crc32) */
/* - Uses Localstorage to store session data like in the original code                        */
/*                                                                                            */
/*                                                                                            */
/*  Example usage:                                                                            */
/*                                                                                            */
/* -> Config variables (set before using the class)                                           */
/*                                                                                            */
/* var G_SERVER_URL  = "http://127.0.0.1:8080";              // Server URL                    */
/* var G_SERVER_ROOT = "root";                               // Server root                   */
/* var G_MAIN_URL    = G_SERVER_URL + '/' + G_SERVER_ROOT;   // Main URL                      */
/*                                                                                            */
/*                                                                                            */
/* -> Login                                                                                   */
/*                                                                                            */
/*  const APP_Login = new SYN_login;                                                          */
/*  APP_Login.login(userName, userPass, F_loginResult);                                       */
/*                                                                                            */
/*  function F_loginResult(result) {                                                          */
/*      if (result) { alert("Login OK"); }                                                    */
/*      else        { alert("Login ERROR"); }                                                 */
/*  }                                                                                         */
/*                                                                                            */
/*  -> Use $.ajax to call your interfaces, etc..                                              */
/*                                                                                            */
/**********************************************************************************************/

class SYN_login {

    login(userName, usarPass, callBack) {
        let servnonce;
        let currDate;
        let clientnonce;
        let dataString;
        let password;
        let charPlusPos;

        let self = this;
    
        this.setAjaxPrefilter();

        this.CloseSession(); // try to close previously opened session
    
        currDate    = new Date();
        clientnonce = currDate.getTime() / (1000 * 60 * 5); // valid for 5*60*1000 ms = 5 minutes;
        clientnonce = sha256("" + clientnonce);
        dataString  = {'UserName': userName};
    
        // First request, to get the servnonce for the user 
        $.ajax({
            type:     "GET",
            dataType: "json",
            url:      G_MAIN_URL + '/auth',
            data:     dataString,
            timeout:  2000,
            success: function(data, textStatus, jqXHR) {
                servnonce  = data.result;            
                password   = sha256(G_SERVER_ROOT + servnonce + clientnonce + userName + sha256('salt' + usarPass));  // Sha256(ModelRoot+Nonce+ClientNonce+UserName+Sha256('salt'+PassWord))
                dataString = {'UserName': userName, 'Password': password, 'ClientNonce': clientnonce};
    
                // Secound request, sending required data including the spicedup password, to get a session
                $.ajax({
                    type:        "GET",
                    dataType:    "json",
                    url:         G_MAIN_URL + '/auth',
                    data:        dataString,
                    crossDomain: true,
                    timeout:     2000, 
                    success: function(data, textStatus, jqXHR) {
                        charPlusPos = data.result.indexOf('+');
                        if (charPlusPos > -1) {

                            // ******************************************
                            // Save relevant session data on localstorage
                            self.setNameValue('SESSION_ID',          data.result.substr(0, charPlusPos));
                            self.setNameValue('SESSION_PRIVATE_KEY', data.result + sha256('salt' + usarPass));
                            self.setNameValue('SESSION_USERNAME',    userName);
                            
                            callBack(true);
                            return true;
                        }
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        callBack(false);
                        return false;
                        if (jqXHR.status == 404) {return false;}  // Not used so far
                    }
    
                });
            },
            error: function() {
                callBack(false);
                return false;
            }
        });
    }



    InitSession() {
        localStorage.removeItem(self.getPrefixed('SESSION_ID'));
        localStorage.removeItem(self.getPrefixed('SESSION_PRIVATE_KEY'));
        localStorage.removeItem(self.getPrefixed('SESSION_LAST_TICK_COUNT'));
        localStorage.removeItem(self.getPrefixed('SESSION_TICK_COUNT_OFFSET'));
        localStorage.removeItem(self.getPrefixed('SESSION_USERNAME'));
        return true;
    }
    
    CloseSession() {
        self = this;

        if (!this.getValue_FromNameAsInt('SESSION_ID')) return;
    
        $.ajax({
            type:     "GET",
            dataType: "json",
            url:      G_MAIN_URL + '/auth',
            data:     {'session': this.getValue_FromNameAsInt('SESSION_ID'), 'UserName': this.getValue_FromName('SESSION_USERNAME')},
            timeout:  2000,
            success:  self.InitSession,
            error:    self.InitSession
        });
    }
    


    // converted from TSQLRestClientURI.SessionSign function
    // expected format is 'session_signature='Hexa8(SessionID)+Hexa8(TimeStamp)+
    // Hexa8(crc32('SessionID+HexaSessionPrivateKey'+Sha256('salt'+PassWord)+
    // Hexa8(TimeStamp)+url))
    GetSessionSignature(url) {
        let currDate;
        let currMsecs;
        let prefix;
        let nonce;
        let ss_id_hex;
        let ss_keyNonceUrl_crc32;
        let ss_keyNonceUrl_hex;
        let final_SIGN;

        currDate  = new Date();
        currMsecs = currDate.getTime();
        prefix    = '?';

        if (currMsecs < this.getValue_FromNameAsInt('SESSION_LAST_TICK_COUNT')) // wrap around 0 after 49.7 days
            this.setNameValue('SESSION_TICK_COUNT_OFFSET', this.getValue_FromNameAsInt('SESSION_TICK_COUNT_OFFSET') + 1 << (32 - 8)); // allows 35 years timing
        
        this.setNameValue('SESSION_LAST_TICK_COUNT', currMsecs);
    
        nonce = currMsecs >>> 8 + this.getValue_FromNameAsInt('SESSION_TICK_COUNT_OFFSET');
        nonce = this.numToHex(nonce);

        ss_id_hex            = this.numToHex(this.getValue_FromNameAsInt('SESSION_ID'));
        ss_keyNonceUrl_crc32 = this.getValue_FromName('SESSION_PRIVATE_KEY') + nonce + url;
        ss_keyNonceUrl_crc32 = this.crc32(   ss_keyNonceUrl_crc32);
        ss_keyNonceUrl_hex   = this.numToHex(ss_keyNonceUrl_crc32);

        // Final signature
        final_SIGN  = ss_id_hex + nonce + ss_keyNonceUrl_hex;  

        // Change prefix if necessary (if the URL already has variables add "&" to set another, keep "?" is this is the only one)
        if (url.indexOf('?') >= 0) 
           prefix = '&';
        
        return  prefix + 'session_signature=' + final_SIGN;
    }
    

    // Set ajaxPrefilter function - will run on every jQuery ajax call to add the SessionSignature   */
    setAjaxPrefilter() {
        self = this;

        $.ajaxPrefilter(function(options, _, jqXHR) {    
            let new_url;
            let session_sign;
        
            if (self.getValue_FromNameAsInt('SESSION_ID') > 0 && options.url.indexOf(G_MAIN_URL) > -1) { // User is authenticated
                new_url = options.url;
                if (options.data && options.type == "GET")
                {
                    new_url      = new_url + '?' + options.data;
                    options.data = null;  // prevents jQuery from adding these to the URL
                }
                session_sign  = self.GetSessionSignature(new_url.substr(G_SERVER_URL.length + 1));
                options.url   = new_url + session_sign;
                options.cache = true; // we don't want anti-cache "_" JQuery-parameter
            }
        });
    }

    

    // Convert number to Hex with 8 caracters
    numToHex(d) {
        let hex = Number(d).toString(16);    // Converts to Hex (base 16)
        
        while (hex.length < 8) {
            hex = "0" + hex;
        }
        return hex;
    }



    /****************************/
    /*     Local Storage        */
    /****************************/
    getPrefixed(name)            { return 'syn_' + name; }
    getValue_FromName(name)      { return localStorage.getItem(this.getPrefixed(name)); }
    setNameValue(name, value)    { return localStorage.setItem(this.getPrefixed(name), value); }
    getValue_FromNameAsInt(name) { return Number(this.getValue_FromName(name)) ? this.getValue_FromName(name) : 0; } // Operator "?" = if then    ":"" = else 



    /*****************************************************************/
    /*                        crc32 functions                        */
    /* https://stackoverflow.com/questions/18638900/javascript-crc32 */
    /*****************************************************************/
    makeCRCTable() {
        let c;
        let crcTable = [];
        for(let n =0; n < 256; n++){
            c = n;
            for(let k =0; k < 8; k++){
                c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
            }
            crcTable[n] = c;
        }
        return crcTable;
    }
    
    crc32(str) {
        let crcTable = window.crcTable || (window.crcTable = this.makeCRCTable());
        let crc = 0 ^ (-1);
    
        for (let i = 0; i < str.length; i++ ) {
            crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
        }
    
        return (crc ^ (-1)) >>> 0;
    };    
  }


/*********************************************/
/*    Check for localstorage functionality   */
/*********************************************/
$(function() {
    if (typeof(localStorage) == 'undefined')
        alert('You do not have HTML5 localStorage support in your browser. Please update or application cannot work as expected');
});

Last edited by imperyal (2018-12-17 17:43:26)

Offline

#81 2018-12-15 03:39:45

edwinsn
Member
Registered: 2010-07-02
Posts: 1,218

Re: Javascript authentication

Thanks for sharing, would be great if put it in a github repository smile


Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.

Offline

#82 2018-12-17 17:44:21

imperyal
Member
Registered: 2018-10-11
Posts: 54

Re: Javascript authentication

No problem, repository created.

https://github.com/imperyal/synopse-login

Offline

#83 2018-12-18 03:37:53

edwinsn
Member
Registered: 2010-07-02
Posts: 1,218

Re: Javascript authentication

imperyal wrote:

No problem, repository created.

https://github.com/imperyal/synopse-login

checked out and starred, thank you!


Delphi XE4 Pro on Windows 7 64bit.
Lazarus trunk built with fpcupdelux on Windows with cross-compile for Linux 64bit.

Offline

#84 2019-10-14 10:18:44

koraycayiroglu
Member
Registered: 2017-02-03
Posts: 55

Re: Javascript authentication

Hi,

I am trying to use this js library in my application. It worked fine with sqlite db but when i use MongoDB, login funtion returned false. Server also responed to /auth parameter as "Bad Request". How could i make it work with MongoDB?

Last edited by koraycayiroglu (2019-10-14 10:18:55)

Offline

#85 2021-07-17 02:19:55

AntonE
Member
Registered: 2012-02-03
Posts: 74

Re: Javascript authentication

Happy to revive an old thread.
I've never worked with Javascript but started to use Flutter (Dart language) and I must say it's starting to grow on me.
I struggled to get authentication to a mORMot server working and almost posted here a few times.

Many thanks to the posters above, I couldn't have done it without you.
FWIW, if anyone need to auth to mORMot from Dart:

import 'dart:convert';
import 'package:archive/archive.dart';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart';

main() async {
  MORMOT_client http_client = MORMOT_client('http://10.1.1.200:8092/','main/');
  await http_client.login('User', 'synopse');

  final List<dynamic> products = await http_client.GetList('product');
  print(products);

  final Map<String,dynamic> product = await http_client.GetItem('product/106');
  print(product);
}

class MORMOT_client {
  MORMOT_client(String this.SERVER_URL,String this.SERVER_MODEL){}

  String SERVER_URL         = '';
  String SERVER_MODEL       = '';
  String SESSION_ID         = '';
  String SESSION_PRIVATEKEY = '';
  String SESSION_USERNAME   = '';
  int    SESSION_TICK_COUNT = 1;
  int    SESSION_START_TIME = 0;

  login(String userName, String userPass) async {
    String clientnonce    = '';
    String servernonce    = '';
    String password       = '';
    String hashedPassword = '';

    SESSION_ID            = '';
    SESSION_PRIVATEKEY    = '';
    SESSION_USERNAME      = userName;
    SESSION_TICK_COUNT    = 1;
    SESSION_START_TIME    = DateTime.now().millisecondsSinceEpoch;

    clientnonce = SHA256((DateTime.now().millisecondsSinceEpoch ~/ (1000 * 60 * 5)).toString()).toString();

    final Map<String, dynamic> sessionResult = await urlToMap(SERVER_URL+SERVER_MODEL+'auth?Username=$userName');
    if (sessionResult.isEmpty) {return;}

    servernonce    = sessionResult['result'];
    hashedPassword = SHA256('salt' + userPass).toString();
    password       = SHA256('main' + servernonce + clientnonce + userName + hashedPassword).toString();

    final Map<String, dynamic> authResult = await urlToMap(SERVER_URL+SERVER_MODEL+'auth?Username=$userName&Password=$password&ClientNonce=$clientnonce');
    if (authResult.isEmpty) {return;}

    String session_str = authResult['result'].toString();
    SESSION_ID         = int.parse(session_str.split('+')[0]).toRadixString(16).padLeft(8, '0');
    SESSION_PRIVATEKEY = session_str + hashedPassword;
    print('SID:$SESSION_ID');
    print('SPK:$SESSION_PRIVATEKEY');
  }

  String GetSessionSignature(String url) {
    String? prefix;
    String? nonce;
    String? keynonce;
    String? final_SIGN;

    SESSION_TICK_COUNT = DateTime.now().millisecondsSinceEpoch - SESSION_START_TIME;
    print('SessionTickCount:$SESSION_TICK_COUNT');

    nonce    = SESSION_TICK_COUNT.toRadixString(16).padLeft(8, '0');
    keynonce = getCrc32(utf8.encode(SESSION_PRIVATEKEY + nonce + SERVER_MODEL+url)).toRadixString(16).padLeft(8, '0');
    print('nonce   :$nonce');
    print('keynonce:$keynonce');
    return SESSION_ID + nonce + keynonce;
  }

  Future<List<dynamic>> GetList(String url)async{
    final String signature = GetSessionSignature(url);
    final String prefix    = (url.indexOf('?')>0) ? '&' : '?';
    final String theUrl    = SERVER_URL+SERVER_MODEL+url+prefix+'session_signature=$signature';
    print('URL:$theUrl');
    final List<dynamic> auth2 = await urlToList(theUrl);
    print(auth2);
    return auth2;
  }
  Future<Map<String,dynamic>> GetItem(String url)async{
    final String signature = GetSessionSignature(url);
    final String prefix    = (url.indexOf('?')>0) ? '&' : '?';
    final String theUrl    = SERVER_URL+SERVER_MODEL+url+prefix+'session_signature=$signature';
    print('URL:$theUrl');
    final Map<String,dynamic> item = await urlToMap(theUrl);
    print(item);
    return item;
  }
}

Digest SHA256(String value){
  var bytes = utf8.encode(value);
  return sha256.convert(bytes);
}

Future<Map<String,dynamic>> urlToMap(String url)async {
  Response r = await get(Uri.parse(url));
  if (r.statusCode != 200) {
    print(r.statusCode);
    print(r.body);
    final Map<String,dynamic> tmp = {};
    return tmp;
  }
  final Map<String, dynamic> res = jsonDecode(r.body);
  return res;
}
Future<List<dynamic>> urlToList(String url)async {
  Response r = await get(Uri.parse(url));
  if (r.statusCode != 200) {
    print(r.statusCode);
    print(r.body);
    final List<dynamic> tmp = [];
    return tmp;
  }
  final List<dynamic> res = jsonDecode(r.body);
  return res;
}

It is a very raw testing unit, needs exception handling etc.
Output an array(list) and object(map):
[{ID: 1}, {ID: 3}, {ID: 4}, {ID: 5}, {ID: 10},...

{ID: 106, Created: 0, Modified: 135522307659, CODE: SLUC40MBPS, DESCRIPTION: SLUC40MBPS...

Last edited by AntonE (2021-07-17 02:25:06)

Offline

#86 2021-07-17 18:31:42

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

Thanks for sharing!

Nice to read this Dart code.

What is your feedback with Flutter?
Is it easy to work with?
Which tutorial do you recommend?

Offline

#87 2022-03-09 15:26:58

moctes
Member
From: Mexico
Registered: 2013-05-11
Posts: 129

Re: Javascript authentication

Good day smile

Is it possible to invalidate an already created user session on the server if the same user login again on another device, browser, etc. let's say the user logs in on one computer, he has his sessionkey for signing requests, then the same user logs in on another computer and now gets another session key and is making signed requests from two places, is it possible that on the second login the first session is invalidated ?

Offline

#88 2022-03-09 17:38:48

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

The same user credential could be used in several places at the same time, each connection with its own session.
This is a feature of the current implementation, and changing it could break a lot of existing code.

So a single session per user could be added as an option.
Do you still use mORMot 1?

Offline

#89 2022-03-09 18:04:53

moctes
Member
From: Mexico
Registered: 2013-05-11
Posts: 129

Re: Javascript authentication

ab wrote:

The same user credential could be used in several places at the same time, each connection with its own session.
This is a feature of the current implementation, and changing it could break a lot of existing code.

Ok, I thought maybe I could do it by changing the value of some property wink  I can possibly do it in some server event, any suggestions?

ab wrote:

So a single session per user could be added as an option.
Do you still use mORMot 1?

Yes, I'm still on 1

Offline

#90 2022-03-09 20:00:41

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,661
Website

Re: Javascript authentication

You have the reOneSessionPerUser execution right which does that:

  // - reOneSessionPerUser will force that only one session may be created
  // for one user, even if connection comes from the same IP: in this case,
  // you may have to set the SessionTimeOut to a small value, in case the
  // session is not closed gracefully

Offline

#91 2022-03-09 20:50:30

moctes
Member
From: Mexico
Registered: 2013-05-11
Posts: 129

Re: Javascript authentication

I will explore that feature

Thank you !

Offline

#92 2022-08-02 07:04:11

AntonE
Member
Registered: 2012-02-03
Posts: 74

Re: Javascript authentication

Hi Ab,
I somehow missed these messages...

ab wrote:

What is your feedback with Flutter?
Is it easy to work with?
Which tutorial do you recommend?


I think Dart and Flutter is amazing and getting better with updates almost every day.

The best tutorial I saw is on AppBrewery "The Complete 2021 Flutter Development Bootcamp with Dart", Flutter/Dart changed a bit since that course, especially with null-safety, but it's not to bad to figure out what need to change. Really good progression into Dart and Flutter, covering basic design all the way to using and writing APIs, Interfaces, Services (in Delphi-speak).
The whole idea of 'Widgets' in Flutter to dynamically build the UI elements (sometimes on each frame), seems weird at first, but it is so to create dynamic, multi-platform apps. Everything, even an Integer is an Object and everything in the UI is a Widget, so the language get really expressive and compact (sometimes).
I'm not turning back, no more expensive Delphi licenses, which I only need for client-side UI apps, but thanks to mORMot, not for server![8-}>

https://appbrewery.com/courses/enrolled/548873

I have not tested/deployed a full-blown non-debug app, to see performance, etc, yet; Almost...

Good luck!
Anton E

Last edited by AntonE (2022-08-02 07:43:13)

Offline

Board footer

Powered by FluxBB