Horse-JWT
July 13, 2026 · View on GitHub
Horse-JWT is an official middleware for generating and validating JWTs in APIs developed with the Horse framework.
We created a channel on Telegram for questions and support:
⭕ Prerequisites
Delphi
- delphi-jose-jwt - JOSE is a standard that provides a general approach to the signing and encryption of any content.
Lazarus
- hashlib4pascal - is an Object Pascal hashing library released under the permissive MIT License which provides an easy to use interface for computing hashes and checksums of data. It also supports state based (incremental) hashing.
⚙️ Installation
Installation is done using the boss install command:
boss install horse-jwt
If you choose to install manually, simply add the following folders to your project, in Project > Options > Resource Compiler > Directories and Conditionals > Include file search path
Delphi
../horse-jwt/src
../delphi-jose-jwt/Source/Common
../delphi-jose-jwt/Source/JOSE
Lazarus
../horse-jwt/src
../HashLib/src/Base
../HashLib/src/Checksum
../HashLib/src/Crypto
../HashLib/src/Hash128
../HashLib/src/Hash32
../HashLib/src/Hash64
../HashLib/src/Include
../HashLib/src/Interfaces
../HashLib/src/KDF
../HashLib/src/NullDigest
../HashLib/src/Nullable
../HashLib/src/Packages
../HashLib/src/Utils
✔️ Compatibility
This middleware is compatible with projects developed in:
- Delphi
- Lazarus
⚡️ Quickstart Delphi
All requests need token authorization.
uses Horse, Horse.JWT;
begin
THorse.Use(HorseJWT('MY-PASSWORD'));
THorse.Post('ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('pong');
end);
THorse.Listen(9000);
end.
Some routes require authentication
uses Horse, Horse.JWT;
begin
THorse.Get('ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('pong');
end);
THorse
.AddCallback(HorseJWT('MY-PASSWORD'))
.Get('private',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('route private');
end);
THorse.Listen(9000);
end.
Skip routes
uses Horse, Horse.JWT;
begin
THorse.Use(HorseJWT('MY-PASSWORD', THorseJWTConfig.New.SkipRoutes(['public'])));
THorse.Get('public',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('public route');
end);
THorse.Get('private',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('private route');
end);
THorse.Listen(9000);
end.
Usage samples
How to create the token?
uses
Horse, Horse.JWT,
JOSE.Core.JWT, JOSE.Core.Builder,
System.DateUtils, System.SysUtils;
begin
THorse.Post('create-token',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LJWT: TJWT;
LToken: String;
begin
LJWT := TJWT.Create();
try
// Enter the payload data
LJWT.Claims.Expiration := IncHour(Now, 1);
// Generating the token
LToken := TJOSE.SHA256CompactToken('MY-PASSWORD', LJWT);
finally
FreeAndNil(LJWT);
end;
// Sending the token
Res.Send(LToken);
end);
THorse.Listen(9000);
end.
How to create a custom Payload?
- Here is an example of a custom payload class.
- For the following examples, it is necessary to use this class.
unit MyClaims;
interface
uses
JOSE.Core.JWT, JOSE.Types.JSON;
type
TMyClaims = class(TJWTClaims)
strict private
function GetId: string;
procedure SetId(const Value: string);
function GetName: string;
procedure SetName(const Value: string);
function GetEmail: string;
procedure SetEmail(const Value: string);
public
property Id: string read GetId write SetId;
property name: string read GetName write SetName;
property Email: string read GetEmail write SetEmail;
end;
implementation
{ TMyClaims }
function TMyClaims.GetId: string;
begin
Result := TJSONUtils.GetJSONValue('id', FJSON).AsString;
end;
procedure TMyClaims.SetId(const Value: string);
begin
TJSONUtils.SetJSONValueFrom<string>('id', Value, FJSON);
end;
function TMyClaims.GetName: string;
begin
Result := TJSONUtils.GetJSONValue('name', FJSON).AsString;
end;
procedure TMyClaims.SetName(const Value: string);
begin
TJSONUtils.SetJSONValueFrom<string>('name', Value, FJSON);
end;
function TMyClaims.GetEmail: string;
begin
Result := TJSONUtils.GetJSONValue('email', FJSON).AsString;
end;
procedure TMyClaims.SetEmail(const Value: string);
begin
TJSONUtils.SetJSONValueFrom<string>('email', Value, FJSON);
end;
end.
How to create a token with the custom payload?
uses
Horse, Horse.JWT,
MyClaims in 'MyClaims.pas',
JOSE.Core.JWT, JOSE.Core.Builder,
System.DateUtils, System.SysUtils;
begin
THorse.Post('create-token',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LJWT: TJWT;
LClaims: TMyClaims;
LToken: String;
begin
// Add the class
LJWT := TJWT.Create(TMyClaims);
try
// Casting using the class
LClaims := TMyClaims(LJWT.Claims);
// Enter the payload data
LClaims.Expiration := IncHour(Now, 1);
LClaims.Id := '1';
LClaims.Name := 'Horse';
LClaims.Email := 'horse@jwt.com';
// Generating the token
LToken := TJOSE.SHA256CompactToken('MY-PASSWORD', LJWT);
finally
FreeAndNil(LJWT);
end;
// Sending the token
Res.Send(LToken);
end);
THorse.Listen(9000);
end.
How to read the custom Payload?
uses
Horse, Horse.JWT,
MyClaims in 'MyClaims.pas',
System.SysUtils;
begin
THorse
.AddCallback(HorseJWT('MY-PASSWORD', THorseJWTConfig.New.SessionClass(TMyClaims))) // Add custom payload class
.Get('ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LClaims: TMyClaims;
LId, LName, LEmail: string;
begin
// Get the Payload information from the Session
LClaims := Req.Session<TMyClaims>;
LId := LClaims.Id;
LName := LClaims.Name;
LEmail := LClaims.Email;
Res.Send(Format('I’m %s and this is my email %s',[LName, LEmail]));
end);
THorse.Listen(9000);
end.
How to read the Payload from Services container (Avoiding Session conflicts)
If you need to use Req.Session for other purposes in your application (e.g. database sessions or custom user profiles), you can retrieve the JWT payload safely from the Req.Services dependency injection container:
uses
Horse, Horse.JWT,
MyClaims in 'MyClaims.pas',
System.SysUtils;
begin
THorse
.AddCallback(HorseJWT('MY-PASSWORD', THorseJWTConfig.New.SessionClass(TMyClaims)))
.Get('ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LClaims: TMyClaims;
begin
// Retrieve claims safely from Req.Services instead of Req.Session
LClaims := Req.Services.Resolve(TMyClaims) as TMyClaims;
Res.Send(Format('Hello %s, your email is %s', [LClaims.Name, LClaims.Email]));
end);
THorse.Listen(9000);
end.
If you are not using a custom SessionClass configuration, the payload will be registered as a standard TJSONObject. You can retrieve it like this:
uses
Horse, Horse.JWT,
System.JSON,
System.SysUtils;
begin
THorse.Use(HorseJWT('MY-PASSWORD'));
THorse.Get('/ping',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LClaims: TJSONObject;
begin
// Retrieve the default JSON payload safely from Req.Services
LClaims := Req.Services.Resolve(TJSONObject) as TJSONObject;
Res.Send('Subject: ' + LClaims.GetValue('sub').Value);
end);
THorse.Listen(9000);
end.
Note
Under Lazarus/FPC, import fpjson instead of System.JSON to work with the default TJSONObject.
How to create public and private routes?
uses
Horse, Horse.JWT,
MyClaims in 'MyClaims.pas',
JOSE.Core.JWT, JOSE.Core.Builder,
System.DateUtils, System.SysUtils;
begin
// Create token
THorse.Post('create-token',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LJWT: TJWT;
LClaims: TMyClaims;
LToken: String;
begin
// Add the class
LJWT := TJWT.Create(TMyClaims);
try
// Casting using the class
LClaims := TMyClaims(LJWT.Claims);
// Enter the payload data
LClaims.Expiration := IncHour(Now, 1);
LClaims.Id := '1';
LClaims.Name := 'Horse';
LClaims.Email := 'horse@jwt.com';
// Generating the token
LToken := TJOSE.SHA256CompactToken('MY-PASSWORD', LJWT);
finally
FreeAndNil(LJWT);
end;
// Sending the token
Res.Send(LToken);
end);
// Route public
THorse.Get('public',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
begin
Res.Send('Route public');
end);
// Route private
THorse
.AddCallback(HorseJWT('MY-PASSWORD', THorseJWTConfig.New.SessionClass(TMyClaims))) // Add custom payload class
.Get('private',
procedure(Req: THorseRequest; Res: THorseResponse; Next: TProc)
var
LClaims: TMyClaims;
LId, LName, LEmail: string;
begin
// Get the Payload information from the Session
LClaims := Req.Session<TMyClaims>;
LId := LClaims.Id;
LName := LClaims.Name;
LEmail := LClaims.Email;
Res.Send(Format('I’m %s and this is my email %s', [LName, LEmail]));
end);
THorse.Listen(9000);
end.
⚡️ Quickstart Lazarus
All requests need token authorization.
{$MODE DELPHI}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Horse, Horse.JWT, SysUtils;
procedure GetPing(Req: THorseRequest; Res: THorseResponse; Next: TNextProc);
begin
Res.Send('Pong');
end;
begin
THorse.Use(HorseJWT('my-private-key'));
THorse.Get('/ping', GetPing);
THorse.Listen(9000);
end.
Some routes require authentication
{$MODE DELPHI}{$H+}
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
{$ENDIF}{$ENDIF}
Horse, Horse.JWT, SysUtils;
procedure GetPing(Req: THorseRequest; Res: THorseResponse; Next: TNextProc);
begin
Res.Send('Pong');
end;
procedure GetPrivate(Req: THorseRequest; Res: THorseResponse; Next: TNextProc);
begin
Res.Send('route private');
end;
begin
THorse.Get('/ping', GetPing);
THorse
.AddCallback(HorseJWT('my-private-key'))
.Get('private', GetPrivate);
THorse.Listen(9000);
end.
⚠️ License
horse-jwt is free and open-source middleware licensed under the MIT License.