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