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.