Delphi implementation of JWT (JSON Web Token) and the JOSE (JSON Object Signing and Encryption) specification suite. This library supports the JWS (JWE is support 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 is the System.Hash unit so OpenSSL is not needed.
The HMAC-RSA algorithm uses necessarily OpenSSL so if you plan to use this algorithm 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 at the fulgan website (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 signing and encryption of any content. JOSE consists of several RFC:
- JWT (JSON Web Token) - describes representation of claims encoded in JSON
- JWS (JSON Web Signature) - describes producing and handling signed messages
- JWE (JSON Web Encryption) - describes producing and handling encrypted messages
- JWA (JSON Web Algorithms) - describes cryptographic algorithms used in JOSE
- JWK (JSON Web Key) - describes format and handling of cryptographic keys in JOSE
- One method call to serialize a token
- One method call to validate and deserialize a compact token
exp
,iat
,nbf
,aud
,iss
,sub
claims validatation: supported- Easy to use
TJOSEConsumer
andTJOSEConsumerBuilder
classes to validate token with a fine granularity - Easy to write custom validators!
NONE algorithm
: supported (but discouraged)HS256
,HS384
,HS512 algorithms
: supportedRS256
,RS384
,RS512 algorithms
: supported (thanks to SirAlex)ES256
,ES384
,ES512
algorithms - not (yet) supported
- This library is not affected by the
None
algorithm vulnerability - This library is not susceptible to the recently discussed encryption vulnerability.
- The WiRL RESTful Library for Delphi
- TMS XData and TMS Sparkle. Read the blog post by Wagner R. Landgraf (he is also a contributor of this project)
- JWE support
- Support of other crypto libraries (TMS Cryptography Pack, etc...)
- Unit Tests
- More examples
This library has been tested with Delphi 10.2 Tokyo, Delphi 10.1 Berlin and Delphi XE6 but with a minimum amount of work it should compile with D2010 and higher
This library has no dependencies on external libraries/units.
Delphi units used:
- System.JSON (DXE6+) (available on earlier Delphi versions as Data.DBXJSON)
- System.Rtti (D2010+)
- System.Generics.Collections (D2009+)
- System.NetEncoding (DXE7+)
- Indy units: IdHMAC, IdHMACSHA1, IdSSLOpenSSL, IdHash
- Please use always the latest version from svn
Simply add the source path "Source/Common" and Source/JOSE" to your Delphi project path and.. you are good to go!
To create a token, simply create an instance of the TJWT
class and set the properties (claims).
The easiest way to serialize, deserialize, verify a token is to use the TJOSE
utility class:
var
LToken: TJWT;
begin
LToken := TJWT.Create;
try
// Token claims
LToken.Claims.IssuedAt := Now;
LToken.Claims.Expiration := Now + 1;
LToken.Claims.Issuer := 'WiRL REST Library';
// Signing and Compact format creation
mmoCompact.Lines.Add(
TJOSE.SHA256CompactToken('my_very_long_and_safe_secret_key', LToken)
);
// Header and Claims JSON representation
mmoJSON.Lines.Add(LToken.Header.JSON.ToJSON);
mmoJSON.Lines.Add(LToken.Claims.JSON.ToJSON);
finally
LToken.Free;
end;
Using the TJWT
, TJWS
and TJWK
classes you can control more setting in the creation of the final compact token.
var
LToken: TJWT;
LSigner: TJWS;
LKey: TJWK;
LAlg: TJOSEAlgorithmId;
begin
LToken := TJWT.Create;
try
LToken.Claims.Issuer := 'Delphi JOSE Library';
LToken.Claims.IssuedAt := Now;
LToken.Claims.Expiration := Now + 1;
// Signing algorithm
case cbbAlgorithm.ItemIndex of
0: LAlg := TJOSEAlgorithmId.HS256;
1: LAlg := TJOSEAlgorithmId.HS384;
2: LAlg := TJOSEAlgorithmId.HS512;
else LAlg := TJOSEAlgorithmId.HS256;
end;
LSigner := TJWS.Create(LToken);
LKey := TJWK.Create(edtSecret.Text);
try
// With this option you can have keys < algorithm length
LSigner.SkipKeyValidation := True;
LSigner.Sign(LKey, LAlg);
memoJSON.Lines.Add('Header: ' + TJSONUtils.ToJSON(LToken.Header.JSON));
memoJSON.Lines.Add('Claims: ' + TJSONUtils.ToJSON(LToken.Claims.JSON));
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
LKey.Free;
LSigner.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
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 id built using the TJOSEConsumerBuilder
utility class using the fluent interface.
var
LConsumer: TJOSEConsumer;
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
LConsumer.Process(Compact);
except
on E: Exception do
memoLog.Lines.Add(E.Message);
end;
LConsumer.Free;