You are not logged in.
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:
The client calls InitializeSecurityContext and transmits generated request to the server.
The server calls AcceptSecurityContext the first time, which generates a reply token that is then sent to the client.
The client receives the token and passes it to InitializeSecurityContext. Then it transmits a new request to the server.
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:
Add a list of the authentication sessions to the TSQLRestServer, as it is made with ordinary sessions - fSessions: TObjectList.
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
hello this topic interesting to me you found a solution?
corchi72
Offline
I do the following:
Code working with Windows SSPI API located in SynSSPIAuth.pas unit.
Added array of the authentication contexts to the TSQLRestServer, as it is made with ordinary sessions.
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
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.
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
Thank you for such quick response!
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
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
very well, but now the SessionUser.LogonName is blank, how do I read the name of SessionUser?
Offline
I've modified the code so that it will fill SessionUser.LogonName as retrieved from server side, when Windows Authentication is used.
Offline
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
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
Yes my previous implementation forced to outdate the session context in case of GetTickCount overflow.
It may indeed be better to refresh the timer.
Offline
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
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
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
ok now it is all clear
thanks
Offline
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
@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
unfortunately there is still an error
So you've fixed TSQLRestServerAuth, as written in the post #11?
Offline
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
I have installed this release [78837c4156] and I use this code for connecting to database " ADatabase.SetUser('' ,'') "
Offline
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.
I have installed this release 78837c4156
You need to fix TSQLRestServer.Auth, as written in the post #11.
Offline
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
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
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
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
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
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
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
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
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
sorry but the error occurs, is successful OnsetUSer and then to the first statement that calls the db unleashes the event OnAuthentificationFailed!
Offline
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
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
ok now it works perfectly well! you're great
thanks corchi
Offline
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
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
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
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
In the current version of the SSPI support code, there are three main problems:
Error when connecting to the server running Windows Server 2008 R2 clients running Windows Server 2003 (error in TSQLRestServerAuthenticationSSPI.ClientSetUser).
Not supported clients running web browser.
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
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
In addition, it is necessary to make changes to SynSSPIAuth.pas.
Otherwise mORMot.pas will not compile.
Offline
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:
Override RetrieveSession and check ConnectionID for session. This would complicate the use of vulnerability, as required fake network traffic.
Use EncryptMessage, provided by SSPI package, to encrypt SessionPrivateKey. This method will not work with the browser.
Maybe there is some other way.
Offline
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
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