Client Authentication Methods
Many PayNet API endpoints require client authentication to verify your identity. PayNet supports two client authentication methods: private_key_jwt and tls_client_auth (mTLS). This section covers both approaches.
Overview
Client authentication proves to the server that your application is authorized to make the request. The method you use depends on your technical preferences and environment.
Both methods are:
- FAPI 2.0 compliant - Required by the PayNet API specification
- Secure - Based on proven cryptographic standards
- Supported across all endpoints - Use either method consistently
Method 1: private_key_jwt (Recommended)
With private_key_jwt, you create a signed JWT (assertion) that proves your identity using your signing private key. This method is self-contained and doesn't rely on TLS-level authentication.
How It Works
- Create a JWT with specific claims identifying your client
- Sign it with your signing private key (PS256 algorithm)
- Include the signed JWT in the
client_assertionrequest parameter - The server verifies the signature using your public signing certificate from your JWKS
JWT Structure
Header:
{
"alg": "PS256",
"kid": "{{KEY_ID}}"
}
Body:
{
"iss": "{{CLIENT_ID}}",
"sub": "{{CLIENT_ID}}",
"aud": "{{TOKEN_ENDPOINT_URL}}",
"iat": {{CURRENT_UNIX_TIMESTAMP}},
"exp": {{CURRENT_UNIX_TIMESTAMP + 600}},
"jti": "{{UUIDV4}}"
}
Field Definitions:
| Field | Description |
|---|---|
alg | Signing algorithm. Must be PS256 (RSASSA-PSS with SHA-256) |
kid | Key ID of your signing certificate (from your JWKS) |
iss | Issuer. Must be your client_id |
sub | Subject. Must be your client_id |
aud | Audience. The token endpoint URL (e.g., from well-known endpoint) |
iat | Issued At. Current Unix timestamp (in seconds) |
exp | Expiration. Typically iat + 600 (10 minutes in future) |
jti | JWT ID. Unique UUIDv4 to prevent replay attacks |
Generating the Client Assertion
Node.js:
const jwt = require('jsonwebtoken');
const fs = require('fs');
const crypto = require('crypto');
const signingKey = fs.readFileSync('/path/to/signing-key.pem', 'utf8');
const now = Math.floor(Date.now() / 1000);
const assertion = {
iss: '{{CLIENT_ID}}',
sub: '{{CLIENT_ID}}',
aud: '{{TOKEN_ENDPOINT_URL}}',
iat: now,
exp: now + 600,
jti: crypto.randomUUID()
};
const options = {
algorithm: 'PS256',
keyid: '{{KEY_ID}}'
};
const clientAssertion = jwt.sign(assertion, signingKey, options);
console.log('client_assertion:', clientAssertion);
Python:
import jwt
import time
import uuid
signing_key = open('/path/to/signing-key.pem', 'r').read()
now = int(time.time())
assertion = {
'iss': '{{CLIENT_ID}}',
'sub': '{{CLIENT_ID}}',
'aud': '{{TOKEN_ENDPOINT_URL}}',
'iat': now,
'exp': now + 600,
'jti': str(uuid.uuid4())
}
headers = {
'alg': 'PS256',
'kid': '{{KEY_ID}}'
}
client_assertion = jwt.encode(assertion, signing_key, algorithm='PS256', headers=headers)
print(f'client_assertion: {client_assertion}')
Including in Request
Include these form parameters in your API request:
client_id={{CLIENT_ID}}
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion={{SIGNED_CLIENT_ASSERTION_JWT}}
Method 2: tls_client_auth (mTLS)
With tls_client_auth, your transport client certificate (presented during TLS handshake) serves as your authentication credential. The server identifies you by the certificate itself, without additional parameters.
How It Works
- Your transport certificate is presented during the TLS/SSL connection
- The server verifies the certificate matches a registered client
- No additional parameters needed in the request body
- The mTLS connection itself proves your identity
Including in Request
Include only the basic client parameter:
client_id={{CLIENT_ID}}
Your mTLS transport certificate is handled at the TLS layer (via --cert and --key in cURL).
Comparison
| Aspect | private_key_jwt | tls_client_auth |
|---|---|---|
| Complexity | Requires JWT generation | Uses existing mTLS setup |
| JWT Required | Yes, must sign for each request | No |
| Key Material Needed | Signing private key | Transport certificate & key |
| Code Examples Needed | Yes (JWT libraries) | No (standard TLS) |
| Recommended For | Modern applications with JWT support | Simpler implementations, legacy systems |
| Use in Postman | Can use pre-request scripts | Simpler—just add cert in Settings |
Implementation Guide
Step 1: Choose Your Method
- Use private_key_jwt if you have JWT libraries available and prefer explicit assertion control
- Use tls_client_auth if you prefer relying on TLS infrastructure
Step 2: Have Required Credentials
- private_key_jwt:
client_id,signing-key.pem,kid(from JWKS) - tls_client_auth:
client_id,transport-certificate.pem,transport-key.pem
Step 3: Apply to Each Endpoint
Sections of this guide will reference this page when client authentication is required. Follow the steps for your chosen method.
Step 4: Use with cURL
Both methods work with mTLS transport (use --cert and --key flags):
private_key_jwt example:
curl -X POST "{{endpoint}}" \
--cert /path/to/transport-certificate.pem \
--key /path/to/transport-key.pem \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={{CLIENT_ID}}" \
-d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion={{JWT}}"
tls_client_auth example:
curl -X POST "{{endpoint}}" \
--cert /path/to/transport-certificate.pem \
--key /path/to/transport-key.pem \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id={{CLIENT_ID}}"
Related Documentation
- Section 1: Before You Begin - Prerequisites - How to obtain signing and transport keys
- Section 4: Obtaining Access Tokens via Client Credentials Grant - Uses client authentication
- Previous
- Well known endpoint