TXDataServerModule not using JWT

Hello!

I've made a TXDataServerModule windows service and set the JWT on the server side. The problem is that if I set an URL address in the browser I don't get rejected because not authorised - the URL returns the objects in the DB, but shouldn't.

I have read the manuals, and copied the written, but it just won't work.

uses 
  ConnectionModule,
  XData.Server.Module,
  System.SysUtils,
  Sparkle.Middleware.Cors,
  Sparkle.Middleware.Compress,
  Sparkle.HttpSys.Server,
  Sparkle.HttpServer.Context,
  Sparkle.Comp.BasicAuthMiddleware,
  Sparkle.Middleware.Jwt,
  XData.OpenAPI.Service,
  XData.SwaggerUI.Service,
  Sparkle.HttpServer.Module;

...

procedure StartServer;
var
  Module : TXDataServerModule;
begin
  if Assigned(SparkleServer) then
     Exit;

  SparkleServer := THttpSysServer.Create;

  Module := TXDataServerModule.Create(Settings.ServerUri, TFireDacMySqlConnection.CreatePool(20));

  // JWT
  Module.AddMiddleware(TJwtMiddleware.Create(Settings.Secret, true));

  // Uncomment line below to enable CORS in the server
  Module.AddMiddleware(TCorsMiddleware.Create);

  // Uncomment line below to allow compressed responses from server
  Module.AddMiddleware(TCompressMiddleware.Create);

  // Swagger UI
  RegisterOpenAPIService;
  RegisterSwaggerUIService;

  SparkleServer.AddModule(Module);

  SparkleServer.Start;
end;

I've also set the permissions on the Entities (example one entity) and the Login service

  [Entity]
  [Table('Dnevi')]
  [Id('FId', TIdGenerator.None)]
  [EntityAuthorize]
  TDan = class
  private
    [Column('Id', [TColumnProp.Required])]
    FId: Integer;
    
    [Column('Ime', [TColumnProp.Required], 20)]
    FIme: string;
  public
    property Id: Integer read FId write FId;
    property Ime: string read FIme write FIme;
  end;

uses
  XData.Security.Attributes, XData.Service.Common;

type
  [ServiceContract]
  IKamijoncinService = interface(IInvokable)
    ['{F0BADD7E-D4AE-4521-8869-8E1860B0A4A0}']
    [HttpGet] function Login(const UserName, Password: string): string;

    [Authorize]
    [HttpGet] procedure SetPosition(VehicleId: integer; Lat, Lon: double);
  end;

implementation

initialization
  RegisterServiceType(TypeInfo(IKamijoncinService));

implementation (simplified):

var
  JWT: TJWT;
  Scopes: string;
begin
  if (UserName <> 'neworldworder') or (Password <> 'supersecretpasswordforworlddomination') then
    raise EXDataHttpUnauthorized.Create('Invalid password');

  JWT := TJWT.Create;
  try
    JWT.Claims.SetClaimOfType<string>('user', UserName);
    Scopes := 'terminal';
    JWT.Claims.SetClaimOfType<string>('scope', Scopes);
    JWT.Claims.Issuer := 'Dath Vader REST server';
    Result := TJOSE.SHA256CompactToken(Settings.Secret, JWT);
  finally
    JWT.Free;
  end;
end;

I couldn't spot anything wrong in your code at first sight.
Can you please reduce your project to a minimum size and send to us so we can reproduce and debug here at our side? Please send steps to reproduce the problem.

We have received the project in private, thank you. But well, the code you sent is different from the code you pasted here.

In your first post you added the JWT middleware this way:

Module.AddMiddleware(TJwtMiddleware.Create(Settings.Secret, true));

In project you have:

Module.AddMiddleware(TJwtMiddleware.Create(Settings.Secret, false));

The second parameter makes all the difference as its purpose is exactly to indicate if the JWT middleware would accept or reject requests where JWT is not present.

Thank you for the feedback, but there is a small problem with this way.

I need to loginb via the Service - so this must be accessible without protection (at least the method Login and LoginPIN), all the other (entites, other services,.. ) must be without protection.

Is this possible?

Yes, it's possible and you can use the OnForbidRequest for that, explicitly allowing specific endpoints to be accessed without JWT.

Here is a sample:

procedure TMyDataModule.OnForbidRequest(Sender: TObject;
  Context: THttpServerContext; var Forbid: Boolean);
begin
  var SegmentsLength := Length(Context.Request.Uri.Segments);
  var Segments := Request.Uri.Segments;
  if (SegmentsLength = 2) and (LowerCase(Segments[0]) = 'KamijoncinService')
    and (LowerCase(Segments[1]) = 'Login') then
    Forbid := False;
end;
1 Like

Perfect!

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.