Blog
All Blog Posts | Next Post | Previous PostSigning and Validating JWTs with Private/Public Keys in Delphi with XData
Saturday, November 16, 2024
In this post, we'll explore how to sign and validate JSON Web Tokens (JWTs) using asymmetric keys (RS256) in XData and Sphinx.
What Are Asymmetric Keys?
Asymmetric encryption uses a pair of keys: a private key and a public key. The private key is used for signing the JWT, while the public key is used for validation. This setup ensures that the private key remains confidential and is never shared, while the public key can be distributed to anyone who needs to verify the JWT.
This differs from symmetric encryption, where the same key is used for both signing and validation. Asymmetric keys offer higher security, particularly in distributed systems where multiple parties need to verify tokens without sharing the private key.
Signing JWTs with Private Key in XData
Generating the Keys
Before diving into the code, youll need to generate the RSA private and public keys. You can use tools like OpenSSL or ssh-keygen
to create these keys in PEM format.
For example, on Windows, you can run:
ssh-keygen -t rsa -b 2048 -m PEM -N "" -f rsa256-private.keyssh-keygen -e -m PEM -f rsa256-private.key > rsa256-public.key
This will generate rsa256-private.key
(private key) and rsa256-public.key
(public key) in PEM format.
In the Code Above: Signing the JWT
In the code snippet below, the JWT is signed using the private key (rsa256-private.key
) if SignWithRSA
is set to True
. If SignWithRSA
is False
, it falls back to signing the JWT with a shared secret (HS256
).
const JWTSecret = 'super_secret_0123456789_0123456789'; SignWithRSA = True; RSAKeyId = 'D00CD046-FEDA-4120-9258-391371649A32'; function TLoginService.Login(const User, Password: string): string; var JWT: TJWT; JWK: TJWK; SigningKey: TArray<Byte>; SigningAlgorithm: TJOSEAlgorithmId; begin if User <> Password then raise EXDataHttpUnauthorized.Create('Invalid password'); JWT := TJWT.Create; try JWT.Claims.SetClaimOfType<string>('custom', 'data'); if SignWithRSA then begin // Load private key from file SigningKey := TFile.ReadAllBytes('rsa256-private.key'); SigningAlgorithm := TJOSEAlgorithmId.RS256; JWT.Header.KeyID := RSAKeyId; end else begin SigningKey := TEncoding.UTF8.GetBytes(JWTSecret); SigningAlgorithm := TJOSEAlgorithmId.HS256; end; JWK := TJWK.Create(SigningKey); try Result := TJOSE.SerializeCompact(JWK, SigningAlgorithm, JWT, False); finally JWK.Free; end; finally JWT.Free; end; end;
Explanation:
- Private Key: If
SignWithRSA
isTrue
, the code loads the private key (rsa256-private.key
) from a file and uses it to sign the JWT using theRS256
algorithm. - Shared Secret: If
SignWithRSA
isFalse
, the JWT is signed with a shared secret (JWTSecret
) using theHS256
algorithm.
This allows flexibility to choose between asymmetric and symmetric encryption based on your application's needs.
Validating JWTs with Public Key
To validate a JWT, you need to ensure it was signed by a trusted entity. In XData, this can be done in the OnGetSecretEx
event, where you check the JWT's header to determine the signing algorithm and key ID.
In the Code Above: Validating the JWT
The following code validates the JWT using the public key (rsa256-public.key
) if the JWT was signed using RS256
. If it was signed using HS256
, it uses the shared secret.
procedure TServerModule.XDataServer1JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext;var Secret: TBytes); begin if JWT.Header.Algorithm = 'HS256' then Secret := TEncoding.UTF8.GetBytes(JWTSecret) else if JWT.Header.Algorithm = 'RS256' then begin if JWT.Header.KeyID = RSAKeyId then Secret := TFile.ReadAllBytes('rsa256-public.key') else raise EJOSEException.CreateFmt('Unknown KeyId in JWT', [JWT.Header.KeyID]); end else raise EJOSEException.CreateFmt('JWS algorithm [%s] is not supported', [JWT.Header.Algorithm]); end;
Explanation:
- Algorithm Check: The code first checks the JWT's
Algorithm
field. If it'sHS256
, it loads the shared secret (JWTSecret
). If it'sRS256
, it checks theKeyID
and loads the corresponding public key (rsa256-public.key
). - Public Key Validation: The public key is used to validate the JWT's signature, ensuring that the token was signed with the corresponding private key.
Configuring JWT Signing with Sphinx
Sphinx allows dynamic configuration of the JWT signing process. The TSphinxConfig.OnGetSigningData
event can be used to specify whether to sign with a private key (RS256) or a shared secret (HS256).
In the Code Above: Configuring JWT Signing in Sphinx
The following code demonstrates how to configure the JWT signing using TSphinxConfig.OnGetSigningData
based on an internal flag (SignWithRSA
):
procedure TServerContainer.SphinxConfigGetSigningData(Sender: TObject; Args: TGetSigningDataArgs);begin if SignWithRSA then begin Args.Data.Algorithm := 'RS256'; Args.Data.KeyId := SomeRandomKeyID; Args.Data.Key := TFile.ReadAllBytes(FileNameContainingPublicKeyInPEMFormat); end else begin Args.Data.Algorithm := 'HS256'; Args.Data.Key := TEncoding.UTF8.GetBytes(SomeRandomSharedSecretKey); end; end;
Explanation:
- Dynamic Signing: The
SphinxConfigGetSigningData
event is triggered when a JWT needs to be signed. IfSignWithRSA
isTrue
, it usesRS256
and loads the private key from a file. Otherwise, it usesHS256
and a shared secret. - Key Selection: The appropriate algorithm and key are set dynamically based on the
SignWithRSA
flag, offering flexibility in choosing between asymmetric and symmetric key signing.
Conclusion
Using asymmetric keys (RS256) to sign and validate JWTs provides enhanced security, ensuring that the private key is never shared. This is especially useful in distributed systems where the public key can be freely shared for validation, while the private key remains secure.
With XData and Sphinx, signing and validating JWTs with either symmetric (HS256) or asymmetric (RS256) keys is straightforward. The ability to dynamically configure the signing process based on your needs allows for a flexible and secure authentication system for your APIs.
By following these practices, you can ensure that your application remains secure and efficient, protecting your API from unauthorized access while supporting a wide range of security configurations.
Wagner Landgraf
This blog post has not received any comments yet.
All Blog Posts | Next Post | Previous Post