mirror of
https://github.com/danieleteti/delphimvcframework.git
synced 2024-11-15 15:55:54 +01:00
Merge pull request #227 from joaoduarte19/jwt_improvements
JWT middleware improvements
This commit is contained in:
commit
0cbcbd51f7
@ -10,6 +10,7 @@ uses
|
||||
Web.WebReq,
|
||||
Web.WebBroker,
|
||||
IdHTTPWebBrokerBridge,
|
||||
IdContext,
|
||||
WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule} ,
|
||||
AppControllerU in 'AppControllerU.pas',
|
||||
MVCFramework.Middleware.JWT in '..\..\sources\MVCFramework.Middleware.JWT.pas',
|
||||
@ -17,6 +18,12 @@ uses
|
||||
|
||||
{$R *.res}
|
||||
|
||||
type
|
||||
TWebBrokerBridgeAuthEvent = class
|
||||
public
|
||||
class procedure ServerParserAuthentication(AContext: TIdContext; const AAuthType, AAuthData: String; var VUsername,
|
||||
VPassword: String; var VHandled: Boolean);
|
||||
end;
|
||||
|
||||
procedure RunServer(APort: Integer);
|
||||
var
|
||||
@ -25,6 +32,7 @@ begin
|
||||
Writeln(Format('Starting HTTP Server or port %d', [APort]));
|
||||
LServer := TIdHTTPWebBrokerBridge.Create(nil);
|
||||
try
|
||||
LServer.OnParseAuthentication := TWebBrokerBridgeAuthEvent.ServerParserAuthentication;
|
||||
LServer.DefaultPort := APort;
|
||||
LServer.Active := True;
|
||||
Writeln('Press RETURN to stop the server');
|
||||
@ -35,6 +43,15 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TWebBrokerBridgeAuthEvent }
|
||||
|
||||
class procedure TWebBrokerBridgeAuthEvent.ServerParserAuthentication(AContext: TIdContext; const AAuthType, AAuthData: String;
|
||||
var VUsername, VPassword: String; var VHandled: Boolean);
|
||||
begin
|
||||
if SameText(AAuthType, 'bearer') then
|
||||
VHandled := True;
|
||||
end;
|
||||
|
||||
begin
|
||||
ReportMemoryLeaksOnShutdown := True;
|
||||
try
|
||||
|
@ -58,7 +58,12 @@ begin
|
||||
TAuthenticationSample.Create,
|
||||
lClaimsSetup,
|
||||
'mys3cr37',
|
||||
'/login'
|
||||
'/login',
|
||||
[TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore, TJWTCheckableClaim.IssuedAt],
|
||||
300,
|
||||
'Authorization',
|
||||
'username',
|
||||
'password'
|
||||
));
|
||||
end;
|
||||
|
||||
|
@ -48,7 +48,7 @@ object Form5: TForm5
|
||||
Font.Style = []
|
||||
ParentFont = False
|
||||
ReadOnly = True
|
||||
TabOrder = 0
|
||||
TabOrder = 1
|
||||
end
|
||||
object Memo2: TMemo
|
||||
Left = 0
|
||||
@ -63,7 +63,7 @@ object Form5: TForm5
|
||||
Font.Style = []
|
||||
ParentFont = False
|
||||
ReadOnly = True
|
||||
TabOrder = 1
|
||||
TabOrder = 2
|
||||
end
|
||||
object Panel1: TPanel
|
||||
Left = 0
|
||||
@ -71,7 +71,7 @@ object Form5: TForm5
|
||||
Width = 647
|
||||
Height = 49
|
||||
Align = alTop
|
||||
TabOrder = 2
|
||||
TabOrder = 0
|
||||
object btnGet: TButton
|
||||
AlignWithMargins = True
|
||||
Left = 171
|
||||
@ -80,8 +80,9 @@ object Form5: TForm5
|
||||
Height = 41
|
||||
Align = alLeft
|
||||
Caption = 'Get a protected resource'
|
||||
TabOrder = 0
|
||||
TabOrder = 1
|
||||
OnClick = btnGetClick
|
||||
ExplicitTop = 2
|
||||
end
|
||||
object btnLOGIN: TButton
|
||||
AlignWithMargins = True
|
||||
@ -91,7 +92,7 @@ object Form5: TForm5
|
||||
Height = 41
|
||||
Align = alLeft
|
||||
Caption = 'Login'
|
||||
TabOrder = 1
|
||||
TabOrder = 0
|
||||
OnClick = btnLOGINClick
|
||||
end
|
||||
end
|
||||
|
@ -49,9 +49,10 @@ begin
|
||||
{ Getting JSON response }
|
||||
lClient := TRESTClient.Create('localhost', 8080);
|
||||
try
|
||||
lClient.UseBasicAuthentication := False;
|
||||
lClient.ReadTimeOut(0);
|
||||
if not FJWT.IsEmpty then
|
||||
lClient.RequestHeaders.Values['Authentication'] := 'bearer ' + FJWT;
|
||||
lClient.RequestHeaders.Values['Authorization'] := 'bearer ' + FJWT;
|
||||
lQueryStringParams := TStringList.Create;
|
||||
try
|
||||
lQueryStringParams.Values['firstname'] := 'Daniele';
|
||||
@ -70,9 +71,12 @@ begin
|
||||
{ Getting HTML response }
|
||||
lClient := TRESTClient.Create('localhost', 8080);
|
||||
try
|
||||
// when the JWT authorization header is named "Authorization", the basic authorization must be disabled
|
||||
lClient.UseBasicAuthentication := False;
|
||||
|
||||
lClient.ReadTimeOut(0);
|
||||
if not FJWT.IsEmpty then
|
||||
lClient.RequestHeaders.Values['Authentication'] := 'bearer ' + FJWT;
|
||||
lClient.RequestHeaders.Values['Authorization'] := 'bearer ' + FJWT;
|
||||
lQueryStringParams := TStringList.Create;
|
||||
try
|
||||
lQueryStringParams.Values['firstname'] := 'Daniele';
|
||||
@ -100,8 +104,8 @@ begin
|
||||
try
|
||||
lClient.ReadTimeOut(0);
|
||||
lClient
|
||||
.Header('jwtusername', 'user1')
|
||||
.Header('jwtpassword', 'user1');
|
||||
.Header('username', 'user1')
|
||||
.Header('password', 'user1');
|
||||
lRest := lClient.doPOST('/login', []);
|
||||
if lRest.HasError then
|
||||
begin
|
||||
|
@ -38,6 +38,21 @@ uses
|
||||
JsonDataObjects;
|
||||
|
||||
type
|
||||
TMVCJWTDefaults = class sealed
|
||||
public const
|
||||
/// <summary>
|
||||
/// Default authorization header name
|
||||
/// </summary>
|
||||
AUTHORIZATION_HEADER = 'Authentication';
|
||||
/// <summary>
|
||||
/// Default username header name
|
||||
/// </summary>
|
||||
USERNAME_HEADER = 'jwtusername';
|
||||
/// <summary>
|
||||
/// Default password header name
|
||||
/// </summary>
|
||||
PASSWORD_HEADER = 'jwtpassword';
|
||||
end;
|
||||
|
||||
TJWTClaimsSetup = reference to procedure(const JWT: TJWT);
|
||||
|
||||
@ -49,6 +64,9 @@ type
|
||||
FSecret: string;
|
||||
FLeewaySeconds: Cardinal;
|
||||
FLoginURLSegment: string;
|
||||
FAuthorizationHeaderName: string;
|
||||
FUserNameHeaderName: string;
|
||||
FPasswordHeaderName: string;
|
||||
protected
|
||||
function NeedsToBeExtended(const JWTValue: TJWT): Boolean;
|
||||
procedure ExtendExpirationTime(const JWTValue: TJWT);
|
||||
@ -65,10 +83,16 @@ type
|
||||
|
||||
procedure OnAfterControllerAction(AContext: TWebContext; const AActionName: string; const AHandled: Boolean);
|
||||
public
|
||||
constructor Create(AAuthenticationHandler: IMVCAuthenticationHandler; AConfigClaims: TJWTClaimsSetup;
|
||||
ASecret: string = 'D3lph1MVCFram3w0rk'; ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore,
|
||||
TJWTCheckableClaim.IssuedAt]; ALeewaySeconds: Cardinal = 300); virtual;
|
||||
constructor Create(
|
||||
AAuthenticationHandler: IMVCAuthenticationHandler;
|
||||
AConfigClaims: TJWTClaimsSetup;
|
||||
ASecret: string = 'D3lph1MVCFram3w0rk';
|
||||
ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore, TJWTCheckableClaim.IssuedAt];
|
||||
ALeewaySeconds: Cardinal = 300;
|
||||
AAuthorizationHeaderName: string = TMVCJWTDefaults.AUTHORIZATION_HEADER;
|
||||
AUserNameHeaderName: string = TMVCJWTDefaults.USERNAME_HEADER;
|
||||
APasswordHeaderName: string = TMVCJWTDefaults.PASSWORD_HEADER); virtual;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@ -81,9 +105,14 @@ uses
|
||||
{ TMVCJWTAuthenticationMiddleware }
|
||||
|
||||
constructor TMVCJWTAuthenticationMiddleware.Create(AAuthenticationHandler: IMVCAuthenticationHandler;
|
||||
AConfigClaims: TJWTClaimsSetup; ASecret: string = 'D3lph1MVCFram3w0rk'; ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore,
|
||||
TJWTCheckableClaim.IssuedAt]; ALeewaySeconds: Cardinal = 300);
|
||||
AConfigClaims: TJWTClaimsSetup;
|
||||
ASecret: string = 'D3lph1MVCFram3w0rk';
|
||||
ALoginURLSegment: string = '/login';
|
||||
AClaimsToCheck: TJWTCheckableClaims = [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore, TJWTCheckableClaim.IssuedAt];
|
||||
ALeewaySeconds: Cardinal = 300;
|
||||
AAuthorizationHeaderName: string = TMVCJWTDefaults.AUTHORIZATION_HEADER;
|
||||
AUserNameHeaderName: string = TMVCJWTDefaults.USERNAME_HEADER;
|
||||
APasswordHeaderName: string = TMVCJWTDefaults.PASSWORD_HEADER);
|
||||
begin
|
||||
inherited Create;
|
||||
FAuthenticationHandler := AAuthenticationHandler;
|
||||
@ -92,6 +121,9 @@ begin
|
||||
FSecret := ASecret;
|
||||
FLoginURLSegment := ALoginURLSegment;
|
||||
FLeewaySeconds := ALeewaySeconds;
|
||||
FAuthorizationHeaderName := AAuthorizationHeaderName;
|
||||
FUserNameHeaderName := AUserNameHeaderName;
|
||||
FPasswordHeaderName := APasswordHeaderName;
|
||||
end;
|
||||
|
||||
procedure TMVCJWTAuthenticationMiddleware.ExtendExpirationTime(const JWTValue: TJWT);
|
||||
@ -129,12 +161,6 @@ var
|
||||
begin
|
||||
lWillExpireIn := SecondsBetween(Now, JWTValue.Claims.ExpirationTime);
|
||||
Result := lWillExpireIn <= JWTValue.LiveValidityWindowInSeconds;
|
||||
// Log.Debug('--------------------------', 'EXPIRE');
|
||||
// Log.DebugFmt('Now : %s', [TimeToStr(Now)], 'EXPIRE');
|
||||
// Log.DebugFmt('ExpirationTime : %s', [TimeToStr(JWTValue.Claims.ExpirationTime)], 'EXPIRE');
|
||||
// Log.DebugFmt('WillExpireIn : %d', [lWillExpireIn], 'EXPIRE');
|
||||
// Log.DebugFmt('LVW : %d', [JWTValue.LiveValidityWindowInSeconds], 'EXPIRE');
|
||||
// Log.DebugFmt('NeedsToBeExtened: %s', [BoolToStr(Result, True)], 'EXPIRE');
|
||||
end;
|
||||
|
||||
procedure TMVCJWTAuthenticationMiddleware.OnAfterControllerAction(AContext: TWebContext; const AActionName: string;
|
||||
@ -167,10 +193,10 @@ begin
|
||||
JWTValue := TJWT.Create(FSecret, FLeewaySeconds);
|
||||
try
|
||||
JWTValue.RegClaimsToChecks := Self.FClaimsToChecks;
|
||||
AuthHeader := AContext.Request.Headers['Authentication'];
|
||||
AuthHeader := AContext.Request.Headers[FAuthorizationHeaderName];
|
||||
if AuthHeader.IsEmpty then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Authentication Required', AContext);
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Authorization Required', AContext);
|
||||
AHandled := True;
|
||||
Exit;
|
||||
end;
|
||||
@ -183,14 +209,6 @@ begin
|
||||
AuthToken := Trim(TNetEncoding.URL.Decode(AuthToken));
|
||||
end;
|
||||
|
||||
// check the jwt
|
||||
// if not JWTValue.IsValidToken(AuthToken, ErrorMsg) then
|
||||
// begin
|
||||
// RenderError(HTTP_STATUS.Unauthorized, ErrorMsg, AContext);
|
||||
// AHandled := True;
|
||||
// end
|
||||
// else
|
||||
|
||||
if not JWTValue.LoadToken(AuthToken, ErrorMsg) then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, ErrorMsg, AContext);
|
||||
@ -200,7 +218,7 @@ begin
|
||||
|
||||
if JWTValue.CustomClaims['username'].IsEmpty then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Invalid Token, Authentication Required', AContext);
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Invalid Token, Authorization Required', AContext);
|
||||
AHandled := True;
|
||||
end
|
||||
else
|
||||
@ -222,7 +240,7 @@ begin
|
||||
if NeedsToBeExtended(JWTValue) then
|
||||
begin
|
||||
ExtendExpirationTime(JWTValue);
|
||||
AContext.Response.SetCustomHeader('Authentication', 'bearer ' + JWTValue.GetToken);
|
||||
AContext.Response.SetCustomHeader(FAuthorizationHeaderName, 'bearer ' + JWTValue.GetToken);
|
||||
end;
|
||||
end;
|
||||
AHandled := False
|
||||
@ -251,8 +269,8 @@ var
|
||||
begin
|
||||
if SameText(AContext.Request.PathInfo, FLoginURLSegment) and (AContext.Request.HTTPMethod = httpPOST) then
|
||||
begin
|
||||
UserName := TNetEncoding.URL.Decode(AContext.Request.Headers['jwtusername']);
|
||||
Password := TNetEncoding.URL.Decode(AContext.Request.Headers['jwtpassword']);
|
||||
UserName := TNetEncoding.URL.Decode(AContext.Request.Headers[FUserNameHeaderName]);
|
||||
Password := TNetEncoding.URL.Decode(AContext.Request.Headers[FPasswordHeaderName]);
|
||||
if (UserName.IsEmpty) or (Password.IsEmpty) then
|
||||
begin
|
||||
RenderError(HTTP_STATUS.Unauthorized, 'Username and password Required', AContext);
|
||||
|
Loading…
Reference in New Issue
Block a user