Skip to main content
Version: 4.0

JSON Web Token (JWT)

JWT Integration Guide.

This guide explains how clients can generate JWT tokens for authentication with our backend system using the RS256 (RSA-SHA256) algorithm for JWT signing.

Overview

Our authentication system uses RS256 (RSA-SHA256) algorithm for JWT token signing. This is an asymmetric encryption method where:

  • Private Key: Kept secret by the client, used to sign JWT tokens
  • Public Key: Shared with our backend, used to verify JWT token signatures

This ensures that only you can create valid tokens, but our backend can verify their authenticity.

Step 1: Generate RSA Key Pair

You need to generate a 2048-bit RSA key pair. This can be done using OpenSSL, which is available on most systems.

Option A: Generate Both Keys at Once

# Generate private key (2048-bit RSA)
openssl genrsa -out jwt_private_key.pem 2048

# Extract public key from private key
openssl rsa -in jwt_private_key.pem -pubout -out jwt_public_key.pem

Expected Output Format

Private Key (jwt_private_key.pem):

-----BEGIN PRIVATE KEY-----
**********
...
-----END PRIVATE KEY-----

Public Key (jwt_public_key.pem):

-----BEGIN PUBLIC KEY-----
**********
...
-----END PUBLIC KEY-----

Step 2: Share Public Key with Backend

You must provide your public key to our backend team so we can verify your JWT tokens.

How to Send the Public Key

  1. Copy the entire public key including the header and footer:

    cat jwt_public_key.pem
  2. Send via secure channel:

    • Support ticket with subject: "JWT Public Key Submission - [Your Company Name]"
    • Use the Developer Portal to submit your public key.
  3. Include the following information:

    Company Name: [Your Company Name]
    Environment:

    Public Key:
    -----BEGIN PUBLIC KEY-----
    [Your public key content here]
    -----END PUBLIC KEY-----

Important Notes

  • NEVER share your private key with anyone, including our backend team
  • The public key is safe to share and will be stored in our system
  • You will receive a confirmation email once your public key is registered
  • Different environments (dev/qa/production) require separate key pairs

Step 3: JWT Libraries for Your Technology Stack

You'll need a JWT library that supports RS256 (RSA-SHA256) signing. Most programming languages have well-maintained JWT libraries:

  • Node.js: jsonwebtoken, jose
  • Python: PyJWT, python-jose
  • Java: jjwt, java-jwt
  • Go: golang-jwt/jwt, go-jose
  • PHP: firebase/php-jwt, lcobucci/jwt
  • .NET/C#: System.IdentityModel.Tokens.Jwt, jose-jwt
  • Ruby: ruby-jwt
  • Rust: jsonwebtoken

Choose the library that's most commonly used in your technology stack and ensure it supports the RS256 algorithm.

Step 4: JWT Payload Structure

Your JWT token must include the following claims in the payload. Use your JWT library to create and sign a token with this structure:

For generic API authentication header formats (API keys and bearer tokens), see the API Docs - Authentication page.

Required Claims

{
"iss": "your company identifier",
"sub": "Merchant ID",
"usr": "user ID",
"aud": "JWT Audience", // prod or test DNS"
"iat": 1707580800, // Created at
"exp": 1707667200, // time of expiring in seconds
"aud_fingerprints": "Public key hash"
}

JWT Claims

The JWT must make a number of claims - all of them standard except for aud_fingerprints (Audience Fingerprints).

FieldTypeNote
algStringThe signing algorithm is RSA signed SHA-256 hash, aliased as RS256. An asymmetric encryption (signing) scheme is required to allow the Kernel Server to be able to validate the token without being able to generate it.
subStringThe Payment Processor Merchant-User ID, or Application ID
issStringThis is a unique (from the perspective of Halo server) identifier for the JWT issuer, agreed upon by the JWT issuer and Synthesis, and configured in advance by Synthesis in the Halo server.
audStringURL of Halo server TLS endpoint, e.g. 'kernelserver.qa.haloplus.io'. This value should be obtained from Synthesis (different per environment) e.g. for QA it would be 'kernelserver.qa.haloplus.io' and for DEV 'kernelserver.za.dev.haloplus.io'
usrStringThe details of the user performing the transaction, typically the username used to sign into the Halo.Go Developer Portal.
iatNumericDateThe UTC timestamp of when the JWT was generated.
expNumericDateThe UTC timestamp of expiration of the JWT.
aud_fingerprintsStringOptional (ANDROID Only). A CSV list of expected SHA-256 fingerprints for the Kernel Server TLS endpoint. This list may contain multiple values to support certificate rotation. In the QA environment, the expected value as of writing this would be: "sha256/zc6c97JhKPZUa+rIrVqjknDE1lDcDK77G41sDo+1ay0="

Implementation Requirements

  1. Load your private key from the PEM file you generated in Step 1
  2. Create the payload with the required claims (see table in Appendix)
  3. Sign the token using the RS256 algorithm with your private key
  4. Set expiration: Use Unix timestamp format (seconds since epoch)

Critical Points

  • Algorithm MUST be RS256 (RSA-SHA256)
  • Token header should be: {"alg": "RS256", "typ": "JWT"}
  • All timestamps (iat, exp) must be Unix timestamps (seconds, not milliseconds)
  • The sub and usr fields must always be available or have the same value
  • Remove any fields that are null or undefined before signing

To validate JWT

To validate values, POST to:

curl --location --request POST 'https://kernelserver.qa.haloplus.io/tokens/checkjwt' \
--header 'Authorization: Bearer {{JWT_TOKEN}}' \
--data ''

Step 5: Generate and Use Tokens

Token Generation Process

  1. Load your private key from the secure location where you stored it
  2. Construct the payload with all required claims
  3. Use your JWT library to sign the payload with RS256 algorithm
  4. The library will automatically:
    • Create the JWT header
    • Encode the payload
    • Sign with your private key
    • Return a complete JWT token in format: header.payload.signature

Use Token when calling the SDK

Include the JWT token in the initialisation message to the SDK:

do {
let capabilities = try HaloSDK.initialize(
authToken: "your_auth_token",
environment: .sandbox
)
print("Device can accept payments: \(capabilities.canAcceptPayments)")
} catch HaloError.deviceNotSupported(let reason) {
// Handle unsupported device
} catch {
// Handle other errors
}

Security Best Practices

Private Key Security

  1. Never commit private keys to version control

    # Add to .gitignore
    echo "jwt_private_key.pem" >> .gitignore
    echo "*.pem" >> .gitignore
    echo "*.key" >> .gitignore
  2. Use environment variables for production:

    // Instead of reading from file in production
    const privateKey = process.env.JWT_PRIVATE_KEY;
  3. Restrict file permissions:

    chmod 600 jwt_private_key.pem
  4. Store securely:

    • Use secret management services (AWS Secrets Manager, HashiCorp Vault, etc.)
    • Encrypt at rest
    • Never share via email/Slack/unsecured channels

Token Security

  1. Set appropriate expiration times:

    • Never use tokens that don't expire
  2. Use HTTPS only:

    • Never send tokens over unencrypted HTTP
    • Always use TLS/SSL for API communication
  3. Implement token refresh:

    • Use refresh tokens for long-lived sessions
    • Rotate tokens regularly
  4. Validate on backend:

    • Our backend will verify the signature using your public key
    • Expired tokens will be rejected
    • Malformed tokens will be rejected

Backend Server examples

const express = require('express');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const path = require('path');

const app = express();
app.use(express.json());

// Load RS256 Private Key
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, 'jwt_private_key.pem'), 'utf8');

app.post('/api/login', (req, res) => {
const { username, password } = req.body;

// 1. Authenticate user (replace with database lookup)
if (username !== 'admin' || password !== 'secret123') {
return res.status(401).json({ error: 'Invalid credentials' });
}

const now = Math.floor(Date.now() / 1000);

// 2. Construct Payload
const payload = {
iss: 'YOUR_ISSUER',
sub: 'MERCHANT_ID',
usr: username,
aud: 'HOST',
iat: now,
exp: now + 86400, // 24 hours
aud_fingerprints: ['AUD'],
ksk_pin: 'KSK'
};

// 3. Sign JWT with RS256
const token = jwt.sign(payload, PRIVATE_KEY, { algorithm: 'RS256' });

return res.json({ access_token: token, token_type: 'Bearer' });
});

app.listen(3000, () => console.log('Node.js Auth Server running on port 3000'));