#1 2012-11-16 11:18:05

Chaa
Member
Registered: 2011-03-26
Posts: 244

Windows authentication on the server

I need support for windows authentication single sign-on on the server.
I tried to implement this by using SSPI functions (InitializeSecurityContext/AcceptSecurityContext).
The calling sequence is as follows:

  1. The client calls InitializeSecurityContext and transmits generated request to the server.

  2. The server calls AcceptSecurityContext the first time, which generates a reply token that is then sent to the client.

  3. The client receives the token and passes it to InitializeSecurityContext. Then it transmits a new request to the server.

  4. The server calls AcceptSecurityContext the second time, mutual authentication has been completed and a  session can begin.

In this case, on server side, I need to save the state between two calls to AcceptSecurityContext.
To achieve this, I see two options:

  1. Add a list of the authentication sessions to the TSQLRestServer, as it is made with ordinary sessions - fSessions: TObjectList.

  2. Add to TAuthSession state of authentication process and mark a TAuthSession as temporary session. For example, not generating private key, so that request processing, other than auth, for such session failed.

How to organize this functionality so that it can be added into the main source tree?

Offline

#2 2012-11-19 13:23:01

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

hello this topic interesting to me you found a solution?

corchi72

Offline

#3 2012-11-20 04:35:04

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

I do the following:

  1. Code working with Windows SSPI API located in SynSSPIAuth.pas unit.

  2. Added array of the authentication contexts to the TSQLRestServer, as it is made with ordinary sessions.

  3. Modified code of TSQLRestServer.Auth and TSQLREstClientURI.SetUser.

If you pass to TSQLRestClientURI.SetUser an empty string as user name, the Windows authentication will be performed.
In this case, in table TSQLAuthUser should be an entry for the windows user, with the LoginName in form DomainName\UserName.

Download source code:
MormotSSPIAuth-20121120.zip
Source code based on e15b7cc80c revision.

Offline

#4 2012-11-20 10:25:35

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

Re: Windows authentication on the server

Thanks A LOT for this great contribution to the framework!

You did follow the mORMot way of coding just perfectly. Better than I may have done myself!
This feature is very well integrated into the main code. Your use method-based callbacks just as expected. And you are even using TDynArray to handle the context array during authentication phase.
smile

I've integrated your patch to the main trunk.
Just added some logs.
See http://synopse.info/fossil/info/d92d2f2de1

Perhaps we may add the ability to automatically add an entry to TSQLAuthUser table, after successful Windows Authentication?

Offline

#5 2012-11-20 10:58:05

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

Thank you for such quick response!

ab wrote:

Perhaps we may add the ability to automatically add an entry to TSQLAuthUser table, after successful Windows Authentication?

In my view, the fact that the user is a member of a domain, do not allow him to work with the application in general.
Giving all users GET access may compromise security, but create them with no rights does not make sense.
The application developer should be able to choose.

Offline

#6 2012-11-20 11:01:02

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

Re: Windows authentication on the server

Chaa wrote:

Giving all users GET access may compromise security, but create them with no rights does not make sense.
The application developer should be able to choose.

You are perfectly right.
Does make sense to me.

So we'll stay with the current implementation.
Thanks!

Offline

#7 2012-11-20 11:08:42

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

very well, but now the SessionUser.LogonName is blank, how do I read the name of SessionUser?

Offline

#8 2012-11-20 12:42:54

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

Re: Windows authentication on the server

I've modified the code so that it will fill SessionUser.LogonName as retrieved from server side, when Windows Authentication is used.

See http://synopse.info/fossil/info/78837c4156

Offline

#9 2012-11-20 13:34:23

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

not this time not work I do not think the code is run, an error occurs first

...
     // now client is authenticated -> create a session for aUserName
      User := TSQLAuthUser.Create(self,'LogonName=?',[aUserName]);
      try
        if User.fID=0 then
          exit;
        CreateNewSession(User,aParams);
      finally
        User.Free;
      end;

Offline

#10 2012-11-20 16:29:50

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

Re: Windows authentication on the server

Which error?

Do you have the corresponding entry in the AuthUser table, with DomainName\LogonName ?

Offline

#11 2012-11-21 04:52:29

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

There is a need to fix check for outdated auth contexts. In the current state it is not working properly.

for i := High(fSSPIAuthContexts) downto 0 do
  if (Now>QWord(fSSPIAuthContexts[i].Created)+QWord(30000)) or
     (fSSPIAuthContexts[i].Created<Int64Rec(Now).Lo) then begin <--
    // outdated or 49 days GetTickCount value rollback
    FreeSecContext(fSSPIAuthContexts[i]);
    CtxArr.Delete(i);
  end;

Best thing to do, as is done in TSQLRestServer.SessionAccess:

for i := High(fSSPIAuthContexts) downto 0 do
    if Now < fSSPIAuthContexts[i].Created then // 32 bit overflow occured
      fSSPIAuthContexts[i].Created := Now else
    if Now>QWord(fSSPIAuthContexts[i].Created)+QWord(30000) then begin
    // free outdated context
    FreeSecContext(fSSPIAuthContexts[i]);
    CtxArr.Delete(i);
  end;

Offline

#12 2012-11-21 08:17:12

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

Re: Windows authentication on the server

Yes my previous implementation forced to outdate the session context in case of GetTickCount overflow.

It may indeed be better to refresh the timer.

See http://synopse.info/fossil/info/8f1b38a05f

Offline

#13 2012-11-21 08:40:06

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

a strange thing happens if I run the debug line U.LogonName: = Values [3];
is assigned blank and SessionUser = nil else if I do F8 from delphi and I do not read the line to debug I get the correct result the SessionUser <> nil and SessionUser.logonname <> blank. Is there any timeout? Do not take me for a madman


      if U.LogonName='' then begin
{$ifdef SSPIAUTH} // try Windows authentication with the current logged user
        InvalidateSecContext(SecCtx);
        try
          while ClientSSPIAuth(SecCtx, InData, OutData) do begin
            // 1st call will return SecCtxId, 2nd call aSessionKey
            if CallBackGet('auth',['UserName','','id',SecCtxId,'data',BinToBase64(OutData)],
              Response,nil,0)<>HTML_SUCCESS then
               exit;
            JSONDecode(Response,['result','id','data','logonname'], Values);
            aSessionKey := Values[0];
            U.LogonName := Values[3];  Here I read blank
            if aSessionKey<>'' then
              break; 
            SecCtxId := Values[1];
            InData := Base64ToBin(Values[2]);
          end;
        finally
          FreeSecContext(SecCtx);
        end;
        // authenticated by Windows on the server side: use the returned
        // aSessionKey to sign the URI, as usual 
{$else}

Offline

#14 2012-11-21 08:40:45

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

ab wrote:

Yes my previous implementation forced to outdate the session context in case of GetTickCount overflow.

Problem even more: on changing GetTickCount, usually every 16 ms, all auth contexts are removed.

Instead of:

fSSPIAuthContexts[i].Created < Int64Rec(Now).Lo

Should be:

Int64Rec(Now).Lo < fSSPIAuthContexts[i].Created

And may be even simpler:

Now < fSSPIAuthContexts[i].Created

Offline

#15 2012-11-21 08:43:02

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

corchi72 wrote:

Is there any timeout? Do not take me for a madman.

Yes, there a timeout in 16 ms, due to bug. See my post above.

Last edited by Chaa (2012-11-21 09:03:23)

Offline

#16 2012-11-21 08:59:22

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

ok now it is all clear

thanks

Offline

#17 2012-11-21 09:08:11

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

unfortunately there is still an error I'm trying to access a server function and asks me to authenticate as if I had not signed in, but I call this function in the event OnSetUser

Server function:

function TFileServer.PropValue(var aParams: TSQLRestServerCallBackParams): Integer;
var
   sPropName:RawUTF8;
   Value:RawUTF8;
//   NuvRegistry1 : TNuvRegistry;
begin
  SQLite3Log.Add.Log(sllInfo,'Start PropValue');
  if not UrlDecodeNeedParameters(aParams.Parameters,'PROPNAME') then begin
    result := 404; // invalid Request
    {$ifNdef SERVICE}
    writeln('invalid Request');
    {$ENDIF}
	  SQLite3Log.Add.Log(sllInfo,'Invalid Request - 404');

from Client :

procedure TFrmToolBarMain.OnSetUser(Sender: TObject);
var
  U : TSQLAuthUser;
  AUser : TSQLUser;
  sStatusLicense:String;
label GotoCheckLicense;
begin
  AUser:=nil;
 
  if CurrentClient.SessionUser<>nil then
  begin
  
   if CurrentClient.SessionUser.LogonName<>'' then

    begin
 //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!from Here I was sent the event "OnAuthentificationFailed"

          ServerTempDir        := UTF8ToString(currentClient.CallBackGetResult('PropValue',['propname',_lbServerTempDir]));          if length(ServerTempDir)>0 then
          begin
             if not (RightStr(ServerTempDir,1)='\') then
               ServerTempDir := format('%s\',[ServerTempDir]);
          end;
...

Offline

#18 2012-11-21 09:38:01

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

Re: Windows authentication on the server

@corchi

Could you step in the code and see what is wrong and why OnAuthenticationFailed is launched?

Did you add the row corresponding to every needed "DomainName\UserName" to AuthUser table, as required?

Offline

#19 2012-11-21 09:41:33

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

corchi72 wrote:

unfortunately there is still an error

So you've fixed TSQLRestServerAuth, as written in the post #11?

Offline

#20 2012-11-21 09:48:24

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

this is my code



procedure TFrmToolBarMain.FormCreate(Sender: TObject);
begin
....
 resultLogin := ShowLogin(currentClient,GetUserFromWindows); //read user and domain of my pc
.....
end;

function TFrmToolBarMain.ShowLogin(var ADatabase:TSQLRestClientUri;UserName:String;Password:String=''):Boolean;
var
  aUserName,aDomain:string;
begin
   result := false;
   if Assigned(ADatabase.SessionUser) and (sametext(ADatabase.SessionUser.LogonName,UserName)) then
         result := true;
  //read user and domain of my pc
   if not(result) and  GetCurrentUserAndDomain(aUserName,aDomain) then
   begin
      if sametext(UserName,aUserName) then
        result := ADatabase.SetUser('' ,'')
   end;

   if not(result) and  TLoginForm.Login('Accesso','Inserire i dati d''accesso',UserName,Password,true,'') then
   begin
           result := ADatabase.SetUser(UserName ,PassWord);
   end;
end;

Offline

#21 2012-11-21 10:07:03

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

I have installed this release [78837c4156] and I use this code for connecting to database " ADatabase.SetUser('' ,'') "

Offline

#22 2012-11-21 10:22:20

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

corchi72 wrote:

this is my code

In the code above, in one place you are comparing a UserName parameter with the SessionUser.LogonName, which has format 'MyDomain\MyUser'.
In a different place you are comparing a UserName parameter with the name of domain user without domain, which is 'MyUser'.
And it is not clear what is passed to the function ShowLogin as a parameter UserName.

corchi72 wrote:

I have installed this release 78837c4156

You need to fix TSQLRestServer.Auth, as written in the post #11.

Offline

#23 2012-11-21 10:39:05

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

the version [78837c4156] already contains the following routine:

function TSQLRestServer.Auth(var aParams: TSQLRestServerCallBackParams): Integer;

procedure CreateNewSession(var User: TSQLAuthUser; var aParams: TSQLRestServerCallBackParams);
var Session: TAuthSession;
begin
  Session := TAuthSession.Create(self,User);
  try
    aParams.Resp := JSONEncode(['result',Session.fPrivateSalt,'logonname',User.LogonName]);
    User := nil; // will be freed by TAuthSession.Destroy
    if fSessions=nil then
      fSessions := TObjectList.Create;
    fSessions.Add(Session);
    Session := nil; // will be freed by fSessions
  finally
    Session.Free;
  end;
end;

var aUserName, aPassWord, aClientNonce, aSalt: RawUTF8;
    User: TSQLAuthUser;
    aSessionID: cardinal;
    i: integer;
{$ifdef SSPIAUTH}
    SecCtxId: Cardinal;
    InDataEnc: RawUTF8;
    CtxArr: TDynArray;
    Now: QWord;
    SecCtxIdx: Integer;
    OutData: RawByteString;
{$endif}
begin
  result := HTML_NOTFOUND;
  if not UrlDecodeNeedParameters(aParams.Parameters,'UserName') then
    exit;
  EnterCriticalSection(fSessionCriticalSection);
  try
    if UrlDecodeNeedParameters(aParams.Parameters,'Session') then begin
      // GET ModelRoot/auth?UserName=...&Session=... -> release session
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeCardinal(aParams.Parameters,'SESSION=',aSessionID,@aParams.Parameters);
      end;
      if (fSessions<>nil) and 
         // allow only to delete its own session - ticket [7723fa7ebd]
         (aSessionID=aParams.Context.Session) then
      for i := 0 to fSessions.Count-1 do
        with TAuthSession(fSessions.List[i]) do
        if fIDCardinal=aSessionID then begin
          SessionDelete(i);
          result := HTML_SUCCESS; // mark success
          break;
        end;
      exit; // unknown session -> error 404
    end else
    if UrlDecodeNeedParameters(aParams.Parameters,'PassWord,ClientNonce') then begin
      // GET ModelRoot/auth?UserName=...&PassWord=...&ClientNonce=... -> handshaking
      while aParams.Parameters<>nil do begin
        UrlDecodeValue(aParams.Parameters,'USERNAME=',aUserName);
        UrlDecodeValue(aParams.Parameters,'PASSWORD=',aPassWord);
        UrlDecodeValue(aParams.Parameters,'CLIENTNONCE=',aClientNonce,@aParams.Parameters);
      end;
      User := TSQLAuthUser.Create(self,'LogonName=?',[aUserName]);
      try
        if User.fID=0 then
          exit; // unknown user -> error 404
        // check if match TSQLRestClientURI.SetUser() algorithm
        aSalt := aClientNonce+User.LogonName+User.PasswordHashHexa;
        if (aPassWord<>SHA256(Model.Root+Nonce(false)+aSalt)) and
           // if didn't try with current nonce, try with previous 5 minutes nonce
           (aPassWord<>SHA256(Model.Root+Nonce(true)+aSalt)) then
          Exit;
        // now client is authenticated -> create a session
        CreateNewSession(User,aParams);
      finally
        User.Free;
      end;
{$ifdef SSPIAUTH}
    end else
    if UrlDecodeNeedParameters(aParams.Parameters,'ID,DATA') then begin
      // GET ModelRoot/auth?UserName=&id=...&data=... -> windows SSPI auth
      while aParams.Parameters<>nil do begin
        UrlDecodeCardinal(aParams.Parameters,'ID=',SecCtxId);
        UrlDecodeValue(aParams.Parameters,'DATA=',InDataEnc,@aParams.Parameters);
      end;
      EnterCriticalSection(fSSPIAuthCriticalSection);
      try
        CtxArr.Init(TypeInfo(TSecContexts), fSSPIAuthContexts);
        // check for outdated auth context
        Now := GetTickCount;
//        for i := High(fSSPIAuthContexts) downto 0 do
//          if (Now>QWord(fSSPIAuthContexts[i].Created)+QWord(30000)) or
//             (fSSPIAuthContexts[i].Created<Int64Rec(Now).Lo) then begin
//            // outdated or 49 days GetTickCount value rollback
//            FreeSecContext(fSSPIAuthContexts[i]);
//            CtxArr.Delete(i);
//          end;
        for i := High(fSSPIAuthContexts) downto 0 do
            if Now < fSSPIAuthContexts[i].Created then // 32 bit overflow occured
              fSSPIAuthContexts[i].Created := Now else
            if Now>QWord(fSSPIAuthContexts[i].Created)+QWord(30000) then begin
            // free outdated context
            FreeSecContext(fSSPIAuthContexts[i]);
            CtxArr.Delete(i);
          end;

        // if no auth context specified, create a new one
        SecCtxIdx := -1;
        if SecCtxId <> 0 then begin
          for i := 0 to High(fSSPIAuthContexts) do
            if fSSPIAuthContexts[i].ID = SecCtxId then begin
              SecCtxIdx := i;
              break;
            end;
          // invalid or outdated id
          if SecCtxIdx<0 then
            exit;
        end;
        if SecCtxIdx<0 then begin
          // 1st call: create SecCtxId 
          if High(fSSPIAuthContexts)>MAXSSPIAUTHCONTEXTS then begin
            {$ifdef WITHLOG}
            SQLite3Log.Family.SynLog.Log(sllUserAuth,
              'Too many Windows Authenticated session in pending state: MAXSSPIAUTHCONTEXTS=%',
              [MAXSSPIAUTHCONTEXTS],self);
            {$endif}
            exit;
          end;
          SecCtxIdx := CtxArr.New; // add a new entry to fSSPIAuthContexts[]
          InvalidateSecContext(fSSPIAuthContexts[SecCtxIdx]);
          fSSPIAuthContexts[SecCtxIdx].ID := fSSPIAuthCounter;
          Inc(fSSPIAuthCounter);
        end;
//        // call SSPI provider
        if ServerSSPIAuth(fSSPIAuthContexts[SecCtxIdx], Base64ToBin(InDataEnc), OutData) then begin
          aParams.Resp := JSONEncode(['result','','id',fSSPIAuthContexts[SecCtxIdx].ID,
            'data',BinToBase64(OutData)]);
          Result := HTML_SUCCESS;
          exit; // 1st call: send back OutData to the client
        end;
        // 2nd call: user was authenticated -> release used context
        ServerSSPIAuthUser(fSSPIAuthContexts[SecCtxIdx], aUserName);
        {$ifdef WITHLOG}
        SQLite3Log.Family.SynLog.Log(sllUserAuth,
          'Windows Authentication success for %',[aUserName],self);
        {$endif}
        FreeSecContext(fSSPIAuthContexts[SecCtxIdx]);
        CtxArr.Delete(SecCtxIdx);
      finally
        LeaveCriticalSection(fSSPIAuthCriticalSection);
      end;
      if aUserName = '' then
        exit;
      // now client is authenticated -> create a session for aUserName
      User := TSQLAuthUser.Create(self,'LogonName=?',[aUserName]);
      try
        if User.fID=0 then
          exit;
        CreateNewSession(User,aParams);
      finally
        User.Free;
      end;
{$endif}
    end else
      // only UserName=... -> return hexadecimal nonce content valid for 5 minutes
      aParams.Resp := JSONEncodeResult([Nonce(false)]);
  finally
    LeaveCriticalSection(fSessionCriticalSection);
  end;
  result := HTML_SUCCESS;
end;

in my code I setUser( username ='', and Password='') is wrong?

I understand,if I want to signin as  ActiveDirectory I must to write username = blank password = blank and as disclosed in the notes:

    // - if SSPIAUTH conditional is defined, and aUserName='', a Windows
    // authentication will be performed - in this case, aPassword is ignored and
    // table TSQLAuthUser shall contain an entry for the logged Windows user,
    // with the LoginName in form 'DomainName\UserName'

Offline

#24 2012-11-21 11:22:06

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

I understand, if I want to signin as ActiveDirectory I must to write username = blank password = blank

Yes, if you call SetUser('', ''), then a Windows authentication will be performed.

Let us assume that function GetUserFromWindows returns 'MyUser'.
And that function GetCurrentUserAndDomain returns 'MyUser' in aUserName.

function TFrmToolBarMain.ShowLogin(var ADatabase:TSQLRestClientUri;UserName:String;Password:String=''):Boolean;
var
  aUserName,aDomain:string;
begin
  result := false;
  //  LogonName = 'MyDomain\MyUser' and UserName = 'MyUser' so Result was False.
  if Assigned(ADatabase.SessionUser) and (sametext(ADatabase.SessionUser.LogonName,UserName)) then
         result := true;
   if not(result) and  GetCurrentUserAndDomain(aUserName,aDomain) then
   begin
      //  UserName = 'MyUser' and aUserName = 'MyUser' so login was performed continuously.
      if sametext(UserName,aUserName) then
        result := ADatabase.SetUser('' ,'')
   end;
...
end;

We can see that the login will be executed continuously.

Offline

#25 2012-11-21 11:25:22

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

after I have authenticated with SetUser('','') if I try to run a simple command ,an error occurs in this line of code:

" if (result.Lo<>HTML_FORBIDDEN) or not Assigned(OnAuthentificationFailed) then " because result.Lo=403

function TSQLRestClientURI.URI(const url, method: RawUTF8;
  Resp, Head, SendData: PRawUTF8): Int64Rec;
var Retry: integer;
    aUserName, aPassword: string;
    aResp: RawUTF8;
begin
  if self=nil then begin
    Int64(result) := HTML_UNAVAILABLE;
    exit;
  end;
  if fServerTimeStampOffset=0 then begin
    fServerTimeStampOffset := 0.0001; // avoid endless recursive call
    if CallBackGet('TimeStamp',[],aResp)=HTML_SUCCESS then
      SetServerTimeStamp(GetInt64(pointer(aResp)));
  end;
  for Retry := -1 to MaximumAuthentificationRetry do begin
    result := InternalURI(SessionSign(url),method,Resp,Head,SendData);
    if (result.Lo<>HTML_FORBIDDEN) or not Assigned(OnAuthentificationFailed) then
      break;
    // "403 Forbidden" in case of authentication failure -> try relog
    if not OnAuthentificationFailed(Retry+2,aUserName,aPassword) or
       not SetUser(StringToUTF8(aUserName),StringToUTF8(aPassword)) then
      break;
  end;
end;

Offline

#26 2012-11-21 13:25:47

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

Re: Windows authentication on the server

@corchi72

As I wrote above, please step within the server side, in TSQLRestServer.Auth() and check which of the 2 calls fails during session context authentication.

Offline

#27 2012-11-21 14:59:33

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

if I write SETUSER ('Domain \ Username', 'password') works, but if I write SETUSER ('','') everything seems to work but if I try to query a table in the db asks me to authenticate again

Offline

#28 2012-11-21 16:06:43

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

Re: Windows authentication on the server

As I wrote above, please step within the server side, in TSQLRestServer.Auth() and check which of the 2 calls fails during session context authentication.

Offline

#29 2012-11-21 17:26:51

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

I do not understand at what point give error, because for me the routine Auth() works. but I wonder if I am the only one with this error because it seems very obvious if you run SETUSER (blank, blank) does not work!

thank corchi

Offline

#30 2012-11-22 03:34:38

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

Try to enable logging. Do you see in the log lines like these?

20121121 15262525  +        TSQLRestServerDB(01E88940).Auth
20121121 15262525 auth          TSQLRestServerDB(01E88940) Windows Authentication success for MyDomain\MyUser
20121121 15262526 auth          TAuthSession(01F1B670) New Admin session MyDomain\MyUser/76 created
20121121 15262526  -        TSQLRestServerDB(01E88940).Auth 00.003.062

Offline

#31 2012-11-22 08:50:27

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

This is my log as you can see there is no error in authentication:

C:\Users\corchi\Documents\Sviluppo\QV.exe 1.0.1.0 (2012-11-22 09:41:42)
Host=PCCORCHI User=corchi CPU=4*9-6-14857 OS=13.1=6.1.7601 Wow64=1 Freq=14318180
TSQLLog 1.17 2012-11-22T09:41:48

20121122 09414810 info  Database Name:  C:\ProgramData\QVDB\DB.Sqlite
20121122 09414810 info  Server Port:  888
20121122 09414810 info  Server Name:  localhost
20121122 09414840 info  TSQLite3HttpServer(034958C0) THttpApiServer(035224B8) initialized
20121122 09414842 info  Server is Started
20121122 09424431 auth  TFileServer(02CE6100) Windows Authentication success for DOMAIN\corchi
20121122 09424431 auth  TAuthSession(02CF84C0) New User session DOMAIN\corchi/76 created

Offline

#32 2012-11-22 14:27:32

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

sorry if I ask but you are working to fix the bug or not, I ask you this because otherwise I will use another road

thanks

Offline

#33 2012-11-22 15:59:36

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

Re: Windows authentication on the server

Did you include the fix as detailed by Chaa in http://synopse.info/forum/viewtopic.php?pid=5643#p5643 ?

I've committed it in http://synopse.info/fossil/ci/525606485c?sbs=0

Offline

#34 2012-11-22 16:17:51

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

sorry but the error occurs, is successful OnsetUSer and then to the first statement that calls the db unleashes the event OnAuthentificationFailed!

Offline

#35 2012-11-22 17:44:32

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

Re: Windows authentication on the server

Are you sure the PasswordHashHexa is '' for this user in AuthUser table?

Offline

#36 2012-11-23 09:21:57

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

my PasswordHashHexa was not blank, because I realized that the table AuthUser not accept blank password. Anyway, now I did some testing and I can create users with passwords blank. But my problem is that I use the same users to access ActiveDirectory is that normally SETUSER, so in this last case, I enter my username and password (Domain \ corchi and Password properly = '123 '). so if I Login with SETUSER ('Domain \ corchi', 123 ') works if I try login to ActiveDirectory with SETUSER ('','') not works because the field in the table AuthUser PasswordHashHexa contains '123' (value not encrypted)

thank corchi72

Last edited by corchi72 (2012-11-23 09:34:00)

Offline

#37 2012-11-23 10:09:54

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

Re: Windows authentication on the server

I've overriden User.PasswordHashHexa with Windows Authentication context ID to ensure work even if not connected to a domain.
See http://synopse.info/fossil/info/9c6cd2ded8

Should help in your case.

Offline

#38 2012-11-23 11:05:19

corchi72
Member
Registered: 2010-12-10
Posts: 232

Re: Windows authentication on the server

ok now it works perfectly well! you're great

thanks corchi

Offline

#39 2012-11-23 12:27:50

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

Re: Windows authentication on the server

Thanks for the feedback.

I'm happy it is working as expected for you.

It was a bit difficult to find out what was wrong exactly, but thanks to your feedback and the wonderful contribution of Chaa, I suspect Windows Authentication is now working great with mORMot.

Offline

#40 2012-12-06 07:13:31

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

In the latest version of mORMot you have added access to HTTP request and response headers, and the ability to return any HTTP code.
This change added ability to authenticate not only for clients, written in Delphi, but also for clients in a web browsers.
The problem of authentication in web browsers is that we can not give them the auth context id.
The browser will automatically request twice the same URI. Instead, we could use the address and port of the client to create auth context id. In this case, we need add to TSQLRestServerURIParams IP-address and port of client, or at the request headers add Remote-Addr and Remote-Port, as web servers do.
Sample code below, tested with Google Chrome and IE 9.
Note that Remote-IP header used incorrectly, as it is provided by the client, not the server, and can not be trusted, and this code is not working with clients in Delphi. This code is for testing only!

{$ifdef SSPIAUTH}
    end else
    if UrlDecodeNeedParameters(Ctxt.Parameters,'ID,DATA') then begin
      // GET ModelRoot/auth?UserName=&id=...&data=... -> windows SSPI auth
      InDataEnc := FindIniNameValue(Pointer(Ctxt.Call.InHead), 'AUTHORIZATION: NEGOTIATE ');
      SecCtxId := FindIniNameValue(Pointer(Ctxt.Call.InHead), 'REMOTEIP: ');
      if InDataEnc = '' then
      begin
        Ctxt.Call.OutHead := 'WWW-Authenticate: Negotiate';
        Ctxt.Call.OutStatus := 401;
        exit;
      end;
      EnterCriticalSection(fSSPIAuthCriticalSection);
      try
        CtxArr.Init(TypeInfo(TSecContexts), fSSPIAuthContexts);
        // check for outdated auth context
        Now := GetTickCount;
        for i := High(fSSPIAuthContexts) downto 0 do
          if Int64Rec(Now).Lo<fSSPIAuthContexts[i].Created then
            // GetTickCount 32 bit potential overflow after 49 days
            fSSPIAuthContexts[i].Created := Int64Rec(Now).Lo else
          if Now>QWord(fSSPIAuthContexts[i].Created)+QWord(30000)  then begin
            FreeSecContext(fSSPIAuthContexts[i]);
            CtxArr.Delete(i);
          end;
        // if no auth context specified, create a new one
        SecCtxIdx := -1;
        for i := 0 to High(fSSPIAuthContexts) do
          if fSSPIAuthContexts[i].ID=SecCtxId then begin
            SecCtxIdx := i;
            break;
          end;
        if SecCtxIdx<0 then begin
          // 1st call: create SecCtxId
          if High(fSSPIAuthContexts)>MAXSSPIAUTHCONTEXTS then
            exit;
          SecCtxIdx := CtxArr.New; // add a new entry to fSSPIAuthContexts[]
          InvalidateSecContext(fSSPIAuthContexts[SecCtxIdx]);
          fSSPIAuthContexts[SecCtxIdx].ID := SecCtxId;
        end;
        // call SSPI provider
        if ServerSSPIAuth(fSSPIAuthContexts[SecCtxIdx], Base64ToBin(InDataEnc), OutData) then begin
          Ctxt.Call.OutHead := 'WWW-Authenticate: Negotiate ' + BinToBase64(OutData);
          Ctxt.Call.OutStatus := 401;
          exit; // 1st call: send back OutData to the client
        end;
        // 2nd call: user was authenticated -> release used context
        ServerSSPIAuthUser(fSSPIAuthContexts[SecCtxIdx], aUserName);
        FreeSecContext(fSSPIAuthContexts[SecCtxIdx]);
        CtxArr.Delete(SecCtxIdx);
      finally
        LeaveCriticalSection(fSSPIAuthCriticalSection);
      end;
      if aUserName='' then
        exit;
      // now client is authenticated -> create a session for aUserName
      User := TSQLAuthUser.Create(self,'LogonName=?',[aUserName]);
      try
        User.PasswordHashHexa := SecCtxId; // override with context
        CreateNewSession(User,Ctxt);
      finally
        User.Free;
      end;
{$endif}

P.S.
TSecContext also changed.

    TSecContext = record
      CredHandle: TSecHandle;
      CtxHandle: TSecHandle;
      ID: RawUTF8;
      Created: Cardinal;
    end;

Last edited by Chaa (2012-12-06 07:15:32)

Offline

#41 2012-12-06 09:46:53

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

Re: Windows authentication on the server

Nice idea.

Does it still work with the Delphi clients?
What is the URI to be used on the client side?

I'm a bit confused about the code.

Offline

#42 2012-12-06 10:34:16

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

No, the code is not working with clients in Delphi. This is just a test code to verify that the authentication in a browser can work.

URI for login:
http://localhost:8080/root/auth?UserName=&id=&data=

Server answer:
{"result":"76+cd5431e12b1d481b0a74b2e7c0a0e609fc0ac4742227332160ab426438c9a670","logonname":"DomainName\\UserName"}

This is for clients like the one described in topic Javascript authentication.

In order to this code can be added to the current implementation, we need to change the auth context id generation algorithm.

Offline

#43 2012-12-06 13:51:11

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

Re: Windows authentication on the server

Chaa wrote:

This is for clients like the one described in topic Javascript authentication.

Could be a good idea, indeed, for corporate AJAX solutions!
Automatic authentication of the current logged user is very user friendly and secure.

Offline

#44 2013-07-15 09:39:11

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

In the current version of the SSPI support code, there are three main problems:

  1. Error when connecting to the server running Windows Server 2008 R2 clients running Windows Server 2003 (error in TSQLRestServerAuthenticationSSPI.ClientSetUser).

  2. Not supported clients running web browser.

  3. Possible security issues, since SessionKey and PasswordHashHexa transmitted as plain text. In default auth sheme, PasswordHashHexa not transmitted over network.

Here is a patch to fix the first two problems.
How to solve the third problem I have not come up with at the moment.

Source code:
MormotSSPIAuth-20130715.zip
Source code based on 4e53b0e50d revision.

Offline

#45 2013-07-15 13:06:48

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

Re: Windows authentication on the server

I've commited your patch.
See http://synopse.info/fossil/info/8f9bb0c3ca
Thanks!

About the 3rd problem, I doubt I understood the issue itself.
How is it working at SMB protocol level?
Here PasswordHashHexa is not the same as with the default protocol, I guess.

Offline

#46 2013-07-16 02:46:38

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

In addition, it is necessary to make changes to SynSSPIAuth.pas.
Otherwise mORMot.pas will not compile.

Offline

#47 2013-07-16 04:20:26

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

NTLMv2, in my opinion, sufficiently protected for use in local area networks.

In the future, it will be possible to switch to use Kerberos, for which it is necessary to register the SPN for the service (setspn.exe) and specify it in the call ClientSSPIAuth and ServerSSPIAuth.

Security issue may be in the URI signature.

session_signature = Hexa8(SessionID)
    + Hexa8(TimeStamp)
    + Hexa8(crc32(SessionID + HexaSessionPrivateKey + Sha256('salt' + PassWord) + Hexa8(TimeStamp) + url))

Let us say, that an attacker can see traffic on the network. In default sheme, we use Sha256('salt' + PassWord) which is known by client and by server, but not by attacker.
If we are using windows authentication means we do not have the user's password. And an attacker knows all the components of the signature.

To solve security issue I see such solutions:

  1. Override RetrieveSession and check ConnectionID for session. This would complicate the use of vulnerability, as required fake network traffic.

  2. Use EncryptMessage, provided by SSPI package, to encrypt SessionPrivateKey. This method will not work with the browser.

Maybe there is some other way.

Offline

#48 2013-07-16 07:58:07

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

Re: Windows authentication on the server

In fact, U.PasswordHashHexa := '' after SSPI authentication.

Perhaps there is some private context on both sides to be used instead of a void string.
What do you think?

Offline

#49 2013-07-16 10:53:29

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

In an ideal world, SSPI function QueryContextAttributes(SECPKG_ATTR_SESSION_KEY) should work, but it in a real world it always returns 'SystemLibraryDTC'.

The only way that I can see at the moment is to use EncryptMessage / DecryptMessage. We can either transfer encrypted SessionPrivateKey or encrypted PasswordHashHexa.

This method will not work with the browser, and for browser we can check ConnectionID for session. Or we can use SSL/HTTPS for 100% security.

Offline

#50 2013-07-19 06:35:43

Chaa
Member
Registered: 2011-03-26
Posts: 244

Re: Windows authentication on the server

Next series of patches for Windows authentication.

Major changes:

  • Added support for Kerberos authentication (in addition to the NTLM). To use Kerberos set SecKerberosSPN variable on client-side to the value of SPN for service, see Kerberos.txt for details.

  • Now SessionPrivateKey transmitted from server to client in encrypted way.

Source code:
MormotSSPIAuth-20130719.zip
Source code based on dec7b69f7d revision.

It would be nice to include information from Kerberos.txt into framework documentation. Text needed rewriting because of my bad English.

About possible security issues, when SessionPrivateKey transmitted over network unencrypted (this affects only browser authentification).
At the moment, it is a common practice. Almost all internet sites use a similar scheme when, after user authentication, server sends unencrypted session identifier. To solve this problem, we can only use SSL/HTTPS connection.

So, the browser authentification remains as before:

URI for login:
http://localhost:8080/root/auth?UserName=&data=

Server answer:
{"result":"76+cd5431e12b1d481b0a74b2e7c0a0e609fc0ac4742227332160ab426438c9a670","logonname":"DomainName\\UserName"}

Offline

Board footer

Powered by FluxBB