How to Use JSON Web Tokens (JWTs) in Lazarus

In a recent project, I was working on a licensing system that had to work entirely offline. For this, I decided to rely on JSON Web Tokens, or JWTs. Lazarus has support for them, but it took me a while to get it working.

First of all, make sure you are running a recent version of FreePascal. The JWT implementation was broken in my version and I had to submit a patch to fix it, which was accepted. If upgrading your version of FreePascal is not feasible, you may consider applying my patch manually.

Afterwards, click the button below to download my demo project. This console application will show you how to create and validate JWTs. Continue reading for code snippets extracted from my demo project.

Shared classes and helpers

We create the TLicenseClaims and TLicenseJWT classes, which are needed to have claims in your JWT apart from the standardized ones such as aud or jti; in this case, we rely on them for the licensee and tier claims. We also implement two helper methods, GetString and GetBytes.

 1interface
 2
 3uses
 4  Classes, SysUtils, fpjwt, fpjwarsa, fprsa, fpJSON, fppem, jsonparser;
 5
 6type
 7  TLicenseClaims = class(TClaims)
 8  private
 9    FLicensee: String;
10    FTier: String;
11  published
12    property licensee: String read FLicensee write FLicensee;
13    property tier: String read FTier write FTier;
14  end;
15
16  TLicenseJWT = class(TJWT)
17  protected
18    function CreateClaims: TClaims; override;
19  end;
20
21  function GetBytes(const FilePath: String): TBytes;
22  function GetString(const FilePath: String): String;
23
24implementation
25
26function TLicenseJWT.CreateClaims: TClaims;
27begin
28  Result := TLicenseClaims.Create;
29end;
30
31function GetString(const FilePath: String): String;
32var
33  List: TStringList;
34begin
35  List := TStringList.Create;
36  try
37    List.LoadFromFile(FilePath);
38    Result := List.Text;
39  finally
40    List.Free;
41  end;
42end;
43
44function GetBytes(const FilePath: String): TBytes;
45var
46  Stream: TMemoryStream;
47begin
48  Stream := TMemoryStream.Create;
49  try
50    Stream.LoadFromFile(FilePath);
51    Result := nil;
52    SetLength(Result, Stream.Size);
53    Stream.ReadBuffer(Result, Stream.Size);
54  finally
55    Stream.Free;
56  end;
57end;       

How to create a JWT

Below is the code to create a JWT signed with RSA.

 1function TJWTExample.CreateJWT: String;
 2var
 3  LicenseJWT: TJWT;
 4  JWTKey: TJWTKey;
 5begin
 6  LicenseJWT := nil;
 7  Result := '';
 8  try
 9    JWTKey := TJWTKey.Create(GetBytes(Location + 'private_key.der')); // or PEMToDER(GetString(Location + 'private_key.pem'), _BEGIN_RSA_PRIVATE_KEY, _END_RSA_PRIVATE_KEY)
10
11    LicenseJWT := TLicenseJWT.Create;
12    LicenseJWT.Claims.aud := 'My Software';
13    LicenseJWT.Claims.jti := TGuid.NewGuid.ToString(true).ToLower;
14    LicenseJWT.Claims.iat := DateTimeToUnix(Now);
15    (LicenseJWT.Claims as TLicenseClaims).licensee := 'George McFly';
16    (LicenseJWT.Claims as TLicenseClaims).tier := 'PERSONAL';
17    LicenseJWT.JOSE.alg := 'RS256';
18    LicenseJWT.JOSE.typ := 'LicenseJWT';
19
20    Result := LicenseJWT.Sign(JWTKey);
21
22    WriteLn('The JWT has been created');
23    WriteLn('-----------------------------------');
24    WriteLn(Result);
25    WriteLn('');
26    WriteLn('Press any key to validate the token.');
27    ReadLn;
28  finally
29    LicenseJWT.Free;
30  end;
31end; 

In the code above, the private key is in der format, but it can also be in pem format. In such a case, you would use the PEMToDER function found in the fppem unit.

How to validate a JWT

Below is the code to validate a JWT signed with RSA.

 1procedure TJWTExample.ValidateJWT(const JWT: String);
 2var
 3  LicenseJWT: TJWT;
 4  JWTKey: TJWTKey;
 5begin
 6  LicenseJWT := nil;
 7  try
 8    JWTKey := TJWTKey.Create(GetBytes(Location + 'public_key.der'));  // or PEMToDER(GetString(Location + 'public_key.pem'), _BEGIN_PUBLIC_KEY, _END_PUBLIC_KEY)
 9
10    LicenseJWT := TLicenseJWT.ValidateJWT(JWT, JWTKey);
11    if (LicenseJWT = nil) then
12      raise Exception.Create('This JWT is invalid.');
13
14    WriteLn('The JWT is valid.');
15    WriteLn('-----------------------------------');
16    WriteLn('jti: ' + LicenseJWT.Claims.jti);
17    WriteLn('aud: ' + LicenseJWT.Claims.aud);
18    WriteLn('iat: ' + LicenseJWT.Claims.iat.ToString);
19    WriteLn('licensee: ' + (LicenseJWT.Claims as TLicenseClaims).licensee);
20    WriteLn('tier: ' + (LicenseJWT.Claims as TLicenseClaims).tier);
21    WriteLn('');
22    WriteLn('Press any key to quit the application.');
23    ReadLn;
24  finally
25    LicenseJWT.Free;
26  end;
27end;

Similarly, the PEMToDER function can be used if the public key is in pem format.


Contact me

To get in touch with me, you can text me or call me on WhatsApp, on Telegram, on Signal, on Session, or on WeChat.


You can also send an email to contact@pascal-bergeron.com or fill out the form below.