Paolo Rossi
Paolo Rossi

I started working as a web developer in 1993 and as a Delphi developer in 1995, I joined Wintech Italia in 2000 and since then a Delphi installation is always present on my PC.

Follow Me

JWT Authentication with Delphi Part 3

- 7 Min Read
Paolo Rossi

Web & Delphi Developer

JWT Authentication with Delphi Part 3


Now that we have introduced the JSON Web Token in Part 1 and dissected it in Part 2, we are ready to fire up Delphi and start writing some code to generate, verify, and validate some JWT tokens.

Cryptographic key considerations

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      "secret"
    )

Nearly all JWT's examples (even mines) use the word 'secret' as the secret key to sign the token but this is problematic because it is too short for the HS256 algorithm (or HS384 or HS512) so it's quite ineffective, in fact this can be quite dangerous from a security perspective.

Speaking of cryptographic hash functions (HMACxxx) we can set up some general rules: given a hash function H with a block size B and a byte-length of hash output L we can say that (RFC 2104):

The key for HMAC can be of any length (keys longer than B bytes are first hashed using H). However, less than L bytes is strongly discouraged as it would decrease the security strength of the function. Keys longer than L bytes are acceptable but the extra length would not significantly increase the function strength. (A longer key may be advisable if the randomness of the key is considered weak)

Bottom line: Please change your signing key accordingly to your security needs!

Enough! let me see the code!

JWT Debugger

Building a signed JWT (JWS)

Using the delphi-jose-jwt library, the creation of the JWT is basically a three-step process:

  1. The first step is the definition of the token's standard claims like Subject, Expiration, JWT ID, and Issuer
  2. The second step is the cryptographic signing of the JWT (JWS)
  3. The final step is the JWT conversion to a URL-safe string, according to the JOSE rules

As you well know, the resulting JWT will be a base64-encoded string divided in 3 parts and signed with the specified key and signature algorithm. After that, you can take the token and (for example) send it to a REST client.

This is a full example that shows the construction of a JWT using the proper JOSE objects. Keep in mind that this is the powerful but "complex" way to build the JWT token but don't worry, the library exposes the TJOSE static class that greatly simplify the construction of the JWT token (we'll see it later).

    procedure TfrmSimple.btnBuildClick(Sender: TObject);
    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);
        try
          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;
          end;
        finally
          LSigner.Free;
        end;
      finally
        LToken.Free;
      end;
    end;

You can see the whole project in the "sample" directory on GitHub, now let's analyze in detail this code. The Delphi classes used to build a JWT token are:

    var
      LToken: TJWT;
      LSigner: TJWS;
      LKey: TJWK;
      LAlg: TJOSEAlgorithmId;

The TJWT class represent the token (filled with the claims), the TJWK represent the key used to sign the token, the TJWS it's the class that does the signing, and the TJOSEAlgorithmId is the type (enum type) of the algorithm used, in short:

The TJWS signs the TJWT's claims using the TJWK key with the TJOSEAlgorithmId algorithm

      LToken := TJWT.Create;
    
      // Here you can fill all of your claims
      LToken.Claims.Issuer := 'Delphi JOSE Library';
      LToken.Claims.IssuedAt := Now;
      LToken.Claims.Expiration := Now + 1;

In order to have a signed token we must create the token and fill the appropriate claims (keep in mind that you can also use a derived class from the TJWTClaims base class)

      // Here you 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;

With the code above we simply choose the algorithm used to create the digital signature of our token.

      // To actually sign the token we create a TJWS object
      LSigner := TJWS.Create(LToken);
    
      // Create the key object
      LKey := TJWK.Create(edtSecret.Text);
    
      // With this option you can have keys < algorithm length
      LSigner.SkipKeyValidation := True;
    
      // Sign the JWT with the chosen key and algorithm
      LSigner.Sign(LKey, LAlg);

And here is the code that does the actual signing and conversion into a compact representation of the JWT:

  1. You have to create a TJWK object (the key) and a TJWS object (the signer)
  2. Then you call the Sign() method to actually sign the JWT (with the key and algorithm as parameters)

The LSigner.SkipKeyValidation := True line is telling the TJWS class that it can skip the validation of the provided key. The validation routine validates against null and (too) short keys.

      // Header: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
      LSigner.Header
    
      // Payload: eyJpc3MiOiJEZWxwaGkgSk9TRSBMaWJyYXJ5IiwiaWF0IjoxNTM0Nzc5NzQxLCJleHAiOjE1MzQ4NjYxNDF9
      LSigner.Payload
    
      // Signature: kevr4S3GBixywzvlZ0L9ZRRJb6osJ5WAiEATu6fuAK8
      LSigner.Signature
    
      // Compact Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJEZWxwaGkgSk9TRSBMaWJyYXJ5IiwiaWF0IjoxNTM0Nzc5NzQxLCJleHAiOjE1MzQ4NjYxNDF9.kevr4S3GBixywzvlZ0L9ZRRJb6osJ5WAiEATu6fuAK8
      LSigner.CompactToken

After that the JWS is ready and you can access the signed token in a compact representation and send it (for example) to a REST client.

Now that we saw the "right" way to build a signed token (JWS) I can show you the TJOSE class that simplifies the process but, keep in mind, that if you need extra control over the creation of your token you can always use the native JOSE classes.

    procedure TfrmSimple.BuildJWT;
    var
      LToken: TJWT;
    begin
      // Create a JWT Object
      LToken := TJWT.Create;
      try
        // Token claims
        LToken.Claims.Subject := 'Paolo Rossi';
        LToken.Claims.IssuedAt := Now;
        LToken.Claims.Expiration := Now + 1;
        LToken.Claims.Issuer := 'Delphi JOSE Library';
    
        // Signing and compact format creation.
        // Please use a different secret key in production!!!!!
        memoCompact.Lines.Add(TJOSE.SHA256CompactToken('mysecretkey', LToken));
      finally
        LToken.Free;
      end;
    end;

As you can see after building your JWT with the needed claims, to get the signed token you only have to call TJOSE.SHA256CompactToken('secret', LToken). Pretty simple right?

Obviously there are other utility methods such as SHA384CompactToken() and SHA512CompactToken() to cover other key lengths. The only caveat here is that using the SHA* methods the SkipKeyValidation property of the TJWS is always set to True.

Decode and verify tokens

The process of verifying a compact token is very simple using the delphi-jose-jwt library but before showing the code I want to clarify some (possible) confusion about the token verification and validation.

Verification vs Validation

In order to ensure that the message (payload) wasn't changed along the way, we have to verify the signature of the token.

After a token (the signature) is verified we can validate its claims so, for example, we can check if the given token is expired or if the subject it's the expected subject, etc...

As you can see the most important thing to do when you receive a token is to verify it because if the signature has been forged or the payload has been tampered with you cannot trust the claims, therefore there is no point in validating them.

Token verification

The verification is a very straightforward process. As usual I will show you the complete snippet using the JOSE classes first:

    procedure TfrmSimple.VerifyTokenComplete;
    var
      LKey: TJWK;
      LToken: TJWT;
      LSigner: TJWS;
    begin
      LKey := TJWK.Create(edtSecret.Text);
      try
        LToken := TJWT.Create;
        try
          LSigner := TJWS.Create(LToken);
          try
            LSigner.SetKey(LKey);
            LSigner.CompactToken := FCompact;
    
            if LSigner.VerifySignature then
              memoJSON.Lines.Add('Token signature is verified')
            else
              memoJSON.Lines.Add('Token signature is not verified')
          finally
            LSigner.Free;
          end;
        finally
          LToken.Free;
        end;
      finally
        LKey.Free;
      end;
    end;

You have to create the TJWK, TJWT, TJWS objects, then you have to set the Key object and the compact token for the LSigner object: LSigner.SetKey(LKey) and LSigner.CompactToken := FCompact. Now you can call the VerifySignature() method and check if the given compact token is a valid token, only after that you can safely access to the token's claims.

    procedure TfrmSimple.VerifyToken;
    var
      LToken: TJWT;
    begin
      // Unpack and verify the token
      LToken := TJOSE.Verify(edtSecret.Text, FCompact);
    
      if Assigned(LToken) then
      begin
        try
          if LToken.Verified then
            memoJSON.Lines.Add('Token signature verified, you can trust the claims')
          else
            memoJSON.Lines.Add('Token signature not verified')
        finally
          LToken.Free;
        end;
      end;
    end;

To further simplify the verification task, you can use the TJOSE utility class. The TJOSE.Verify() method takes the key and the compact token representation and returns a TJWT object but, remember, that you have to check if the the TJWT itself is verified before accessing its claims.

Claims validation

The validation of the token's claims cannot be fully "automated" because it's the user choice (and responsibility) to set the claims and to check them.

The JWT standard (RFC 7519) defines some "standard" claims as I explained in Part 2. In a "real" situation some claims are very important, like the Expiration Time exp, the Subject sub or the Issued At iat, etc... So when you receive back the token, after the signature verification you must also validate the claims found in the token.

      if LToken.Verified then
      begin
        // Claims validation (for more see the JOSE.Consumer unit)
    
        if LToken.Claims.Subject <> 'Paolo Rossi' then
          memoJSON.Lines.Add('Subject [sub] claim value doesn''t match expected value');
    
        if LToken.Claims.Expiration > Now then
          memoJSON.Lines.Add('Expiration time passed: ' + DateTimeToStr(LToken.Claims.Expiration));
      end;

This is only an example of claims validation but you can, of course, add your own logic to the validation process. In addition you can also create JWTs with custom claims and therefore you have to do some custom validations.

That said, the delphi-jose-jwt library contains some classes that greatly simplify the claims validation process. You can find the classes in the JOSE.Consumer.pas and JOSE.Consumer.Validators.pas units (more on this in the next article)

JWT Comsumer

Conclusion

In this part we have described how to build, decode, verify and validate a JWT token. In the next part I will explain in detail the use of custom claims in a JWT token (TJWTClaimsClass class reference) and the JOSE.Consumer.* classes to validate the standard claims that strictly follow the JWT standard.