Security
Delphi implementation of JWT (JSON Web Token) and the JOSE (JSON Object Signing and Encryption) specification suite. This library supports the JWS (JWE support is planned) compact serializations with several JOSE algorithms.
Prior to Delphi 10 Seattle the the HMAC-SHA algorithm uses OpenSSL through the Indy library, so in order to generate the token you should have the OpenSSL DLLs in your server system.
In Delphi 10 Seattle or newer Delphi versions the HMAC algorithm is also in the System.Hash unit so OpenSSL is not needed.
The HMAC-RSA(ECDSA) algorithm uses necessarily OpenSSL so if you plan to use these algorithms to sign your token you have to download and deploy OpenSSL (on the server).
Please keep in mind that the client doesn't have to generate or verify the token (using SHA or RSA) so on the client-side there's no need for the OpenSSL DLLs.
If you need the OpenSSL library on the server, you can download the package directly to the Indy's GitHub project page (keep in mind to always update to the latest version and to match you application's bitness)
JOSE is a standard that provides a general approach to the signing and encryption of any content. JOSE consists of several RFC:
Algorithms | Supported |
---|---|
exp | ✔️ |
iat | ✔️ |
nbf | ✔️ |
aud | ✔️ |
iss | ✔️ |
jti | ✔️ |
typ | ✔️ |
TJOSEProducer
and TJOSEProducerBuilder
(alias TJOSEProcess
) classes to build a new compact token with many optionsTJOSEConsumer
and TJOSEConsumerBuilder
classes to validate token with a fine granularityAlgorithms | Supported |
---|---|
None | ✔️ don't use it! 💀 |
HS256 | ✔️ |
HS384 | ✔️ |
HS512 | ✔️ |
RS256 | ✔️ updated! 🔥 |
RS384 | ✔️ updated! 🔥 |
RS512 | ✔️ updated! 🔥 |
ES256 | ✔️ new! 🌟 |
ES384 | ✔️ new! 🌟 |
ES512 | ✔️ new! 🌟 |
ES256K | ✔️ new! 🌟 |
None
algorithm vulnerabilityThis library has been tested with Delphi 12 Athens, Delphi 11 Alexandria, Delphi 10.4 Sydney, Delphi 10.3 Rio, Delphi 10.2 Tokyo but with some work it should compile with DXE6 and higher but I have not tried or tested this, if you succeed in this task I will be happy to create a branch of your work!
This library has no dependencies on external libraries/units.
Delphi units used:
Simply add the source path "Source/Common" and Source/JOSE" to your Delphi project path and.. you are good to go!
Using the boss install
command:
$ boss install github.com/paolo-rossi/delphi-jose-jwt
To create a token, simply create an instance of the TJWT
class and set the properties (claims).
The easiest way to build a JWT token (compact representation) is to use the IJOSEProducer
interface:
uses
JOSE.Producer;
var
LResult: string;
begin
LResult := TJOSEProcess.New
.SetIssuer('Delphi JOSE Library')
.SetIssuedAt(Now)
.SetExpiration(Now + 1)
.SetAlgorithm(LAlg)
.SetKey(TJOSEAlgorithmId.HS256)
.Build
.GetCompactToken
;
memoCompact.Lines.Add(LResult);
end;
Another way to serialize, deserialize, verify a token is to use the TJOSE
utility class:
uses
JOSE.Core.JWT,
JOSE.Core.Builder;
var
LToken: TJWT;
LCompactToken: string;
begin
LToken := TJWT.Create;
try
// Token claims
LToken.Claims.Issuer := 'WiRL REST Library';
LToken.Claims.Subject := 'Paolo Rossi';
LToken.Claims.Expiration := Now + 1;
// Signing and Compact format creation
LCompactToken := TJOSE.SHA256CompactToken('my_very_long_and_safe_secret_key', LToken);
mmoCompact.Lines.Add(LCompactToken);
finally
LToken.Free;
end;
Using the TJWT
, TJWS
and TJWK
classes you have more control over the creation of the final compact token.
var
LToken: TJWT;
LSigner: TJWS;
LKey: TJWK;
LAlg: TJOSEAlgorithmId;
begin
LToken := TJWT.Create;
try
// Set your claims
LToken.Claims.Subject := 'Paolo Rossi';
LToken.Claims.Issuer := 'Delphi JOSE Library';
LToken.Claims.IssuedAt := Now;
LToken.Claims.Expiration := Now + 1;
// Choose the signing algorithm
case cbbAlgorithm.ItemIndex of
0: LAlg := TJOSEAlgorithmId.HS256;
1: LAlg := TJOSEAlgorithmId.HS384;
2: LAlg := TJOSEAlgorithmId.HS512;
else LAlg := TJOSEAlgorithmId.HS256;
end;
// Create your key from any text or TBytes
LKey := TJWK.Create(edtSecret.Text);
try
// Create the signer
LSigner := TJWS.Create(LToken);
try
// With this option you can have keys < algorithm length
LSigner.SkipKeyValidation := True;
// Sign the token!
LSigner.Sign(LKey, LAlg);
memoCompact.Lines.Add('Header: ' + LSigner.Header);
memoCompact.Lines.Add('Payload: ' + LSigner.Payload);
memoCompact.Lines.Add('Signature: ' + LSigner.Signature);
memoCompact.Lines.Add('Compact Token: ' + LSigner.CompactToken);
finally
LSigner.Free;
end;
finally
LKey.Free;
end;
finally
LToken.Free;
end;
Unpacking and verifying tokens is simple.
You have to pass the key and the token compact format to the TJOSE.Verify
class function
var
LKey: TJWK;
LToken: TJWT;
begin
// Create the key from a text or TBytes
LKey := TJWK.Create('my_very_long_and_safe_secret_key');
// Unpack and verify the token!
LToken := TJOSE.Verify(LKey, FCompactToken);
if Assigned(LToken) then
begin
try
if LToken.Verified then
mmoJSON.Lines.Add('Token signature is verified')
else
mmoJSON.Lines.Add('Token signature is not verified')
finally
LToken.Free;
end;
end;
end;
Using the new class TJOSEConsumer
it's very easy to validate the token's claims. The TJOSEConsumer
object is built with the TJOSEConsumerBuilder
utility class using the fluent interface.
var
LConsumer: IJOSEConsumer;
begin
LConsumer := TJOSEConsumerBuilder.NewConsumer
.SetClaimsClass(TJWTClaims)
// JWS-related validation
.SetVerificationKey(edtConsumerSecret.Text)
.SetSkipVerificationKeyValidation
.SetDisableRequireSignature
// string-based claims validation
.SetExpectedSubject('paolo-rossi')
.SetExpectedAudience(True, ['Paolo'])
// Time-related claims validation
.SetRequireIssuedAt
.SetRequireExpirationTime
.SetEvaluationTime(IncSecond(FNow, 26))
.SetAllowedClockSkew(20, TJOSETimeUnit.Seconds)
.SetMaxFutureValidity(20, TJOSETimeUnit.Minutes)
// Build the consumer object
.Build();
try
// Process the token with your rules!
LConsumer.Process(Compact);
except
// (optionally) log the errors
on E: Exception do
memoLog.Lines.Add(E.Message);
end;