JWT Authentication Guide
What is JWT?
JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties. It is widely used for authentication and authorization in web applications.
A JWT typically consists of three parts:
- Header – contains the type of the token (
JWT
) and the signing algorithm (HS256
, etc.). - Payload – contains the claims, i.e., data such as
user
,exp
(expiration), etc. - Signature – a hash of the header and payload, signed using a secret key.
The final token is encoded as:
header.payload.signature
Use Case in Our Project
We use JWT authentication to secure HTTP requests to our local JSON-RPC API betweedn Zano desktop App and Zano Extension (Zano Companion). Each request includes a signed JWT in the Zano-Access-Token
header, which is verified by the server.
You can enable JWT authentification in simplewallet as well by adding --jwt-secret=hsjejkcdskndspo230XASIijksk123i9x5
when simplewallet run in server mode (with --rpc-bind-port=PORT_NUM option).
Code example on nodejs
Here is a review of example of using JWT auth to call getbalance function from wallet.
Dependencies
axios
– for sending HTTP requests.node-forge
– for cryptographic operations (HMAC, SHA-256, etc.).
Install them:
npm install axios node-forge
JWT Generation Flow
1. Shared Secret
Define your shared JWT secret (must match the server's configuration):
const JWT_SECRET = 'hiwejkcddkndspo230XASIijksk123i9x5';
2. Create JWT Token
function createJWSToken(payload, secret) {
const header = { alg: 'HS256', typ: 'JWT' };
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64').replace(/=/g, '');
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64').replace(/=/g, '');
const signature = forge.hmac.create();
signature.start('sha256', secret);
signature.update(`${encodedHeader}.${encodedPayload}`);
const encodedSignature = forge.util.encode64(signature.digest().getBytes()).replace(/=/g, '');
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
}
3. Generate Body Hash
Before creating the token, we hash the request body:
const md = forge.md.sha256.create();
md.update(httpBody);
const bodyHash = md.digest().toHex();
4. Build Payload
The payload contains:
body_hash
: hash of the request body.user
: fixed identifier.salt
: random 64-character string to avoid replay attacks.exp
: expiration timestamp (e.g., 60 seconds from now).
const payload = {
body_hash: bodyHash,
user: 'zano_extension',
salt: generateRandomString(64),
exp: Math.floor(Date.now() / 1000) + 60,
};
5. Generate Final JWT
const token = createJWSToken(payload, JWT_SECRET);
Sending the Authenticated Request
axios.post('http://127.0.0.1:11111/json_rpc', requestData, {
headers: {
'Content-Type': 'application/json',
'Zano-Access-Token': token,
},
})
.then(response => {
console.log('Response:', response.data);
})
.catch(error => {
if (error.response) {
console.error('Error Response:', error.response.status, error.response.data);
} else {
console.error('Request Error:', error.message);
}
});
Notes
- Ensure your server validates:
- JWT signature using the same secret (
HS256
). exp
is not expired.body_hash
matches actual request body content.
- JWT signature using the same secret (
- JWT tokens should be short-lived (e.g., 60 seconds) to reduce replay attack risks.
Example JSON-RPC Request
{
"jsonrpc": "2.0",
"id": 0,
"method": "getbalance",
"params": {}
}
Summary
JWT provides a secure and compact mechanism for authenticating API requests. In this project, we:
- Hash the request body.
- Create a signed JWT with a time-limited payload.
- Send it in the
Zano-Access-Token
HTTP header.
This ensures authenticity, integrity, and freshness of every client request.