Non-repudiation API Authentication
In non-repudiation API authentication, the authenticity of the request is verified using a digital signature based on the use of a private and public key pair: The private key is a secret key that is known only to you. You use this key to sign the requests you send to the Payments API. PaymentsOS then uses the public key to verify the signature you generated using the corresponding private key, thus ensuring the authenticity and integrity of the data sent in the request.
To use non-repudiation API authentication with your requests, you need to follow three steps:
-
Create an asymmetric key pair (that is, a public and private key).
-
Configure your Business Unit(s) to use non-repudiation API authentication.
-
Sign the payment requests you send to the Payments API.
Let’s take a look at each of those steps in detail.
Step 1: Create an Asymmetric Key Pair
An asymmetric key pair is pair of cryptographic keys that consists of a public key and a private key. To generate the keys, you can choose any cryptographic software library or tool that supports public-key cryptography. Here’s an example of how you might generate a new RSA key pair using OpenSSL in a Linux command line (note that currently only the RSA algorithm is supported):
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
The public key will be used by PaymentsOS to verify your signed request (you will configure your Business Units to use the public key in the next step). Beware that the private key should be kept confidential and not shared with anyone else (including PayU).
Step 2: Configure your Business Unit(s) to use Non-repudiation API Authentication
By default, PaymentsOS authenticates your API requests using basic authentication with the Enterprise-platform API keys defined in your Business Unit’s configuration settings. To use non-repudiation API authentication, you must update your Business Unit’s configuration settings and indicate that you want to use non-repudiation API authentication for transactions handled by that Business Unit.
Configuring your Business Unit(s) to use non-repudiation API authentication is easy. Just follow the steps below:
-
Invoke the Create a Business Unit or Update a Business Unit API and pass a value of
non_repudiation
in theauthentication_type
field:{ "id": "com.bussinessunit-1.mycomp", "default_processor": "f0a97e7c-efc4-4859-aca3-6bc7e30a6f7c", "description": "My Business Unit", "authentication_type": "non_repudiation" }
Can a Business Unit use both authentication options?
Yes. You can configure a Business Unit to support both Enterprise-platform API Keys authentication and non-repudiation API authentication. Applying the two authentication options will ensure authentication will not fail in the event that your public key expires (as PaymentsOS will default back to the Enterprise-platform API Keys authentication).
To apply both authentication options, pass a value of all
in the authentication_type
field. PaymentsOS will then apply the authentication type based on the authentication value passed in the header of your payment requests.
- Base64 encode the public key you generated in step 1 and then call the Assign a Public Key to the Account API to assign the public key to your PaymentsOS account. In the request body pass a descriptive name for your key, as well as the key itself:
{
"name": "My Base64 Encoded Public Key",
"key":"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KICAgICAgICAgICAg...",
"type": "public_key"
}
The response of this call will include a kid
(key ID) representing your public key. The response will look something like this:
{
"name": "My Base64 Encoded Public Key",
"kid": "03b941e3-3615-47a5-a046-766d5a4544e3",
"key": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KICAgICAgICAgICAg...",
"created_timestamp": "2023-08-22"
}
-
With the
kid
at hand, invoke the Assign a Public Key to a Business Unit API to assign the public key to your Business Unit(s). Notice that you pass thekid
as a path parameter, while you pass the Business Units to which you want to assign the public key in theapp_ids
array in the request body:{ "app_ids": [ "com.bussinessunit-1.mycomp", "com.bussinessunit-2.mycomp" ] }
Want to remove a public key from a Business Unit?
You can always remove the public key assigned to your Business Unit(s). To do so, simply call the Remove Public Keys from a Business Unit API.Step 3: Sign the payment requests you send to the Payments API
Steps 1 and 2 are one-off steps, as you need to do them only once. Signing the payment requests, however, is a recurring step: you need to sign each request sent to the Payments API.
Signing the payment requests is a two-step process:
-
Construct a JSON Web Token (JWT).
-
Pass the JWT in the header of the Payments API request.
Constructing a JSON Web Token (JWT)
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. If you’re not familiar with JWT’s, then you can read more on the jwt.io introduction site.
When constructing the JWT, you first include a header and payload and then sign the JWT with your private key. Make sure the header and payload adhere the specifications listed below (or if you’re already familiar with the specifications, just head right over to a code sample that shows you how to construct and sign the JWT).
Header
Include the attributes listed in the table below.
Attribute | Specifications |
---|---|
alg |
The algorithm used for signing the JWT. Should be one of the following: RS256 , RS384 , RS512 , PS256 , PS384 , PS512 |
typ |
The type of the JWT. Must be JWT |
kid |
The ID representing your public key. This ID was returned to you when you called the Assign a Public Key to the Account API to assign the public key to your account. |
Payload
Include the attributes listed in the table below.
Attribute | Specifications |
---|---|
iat |
The time at wich the JWT was issued, in Epoch format. |
exp |
The expiration time of the JWT, in Epoch format. It represents the date and time after which the token should no longer be considered valid. A valid expiration date is within range of 0-20 minutes after the issue time. |
hashed_request |
A sha512 hashed value that includes the URI of the payment request you're invoking and the entire request body. Format: URI + '.' + the request body. Important notes:
|
Here’s an example of creating the hashed_request
in Node.js:
const requestUri = "/payments"
const requestBody = {
"amount": 0,
"currency": "USD"
....
}
const {createHash} = require('crypto');
const bodyToHash = requestBody ? JSON.stringify(requestBody) : '';
const hashed_request = createHash('sha512').update(${requestUri}.${bodyToHash}).digest('HEX');
Signing the JWT
With the header and payload in place, sign the JWT with your private key like so:
const JWT = require('jsonwebtoken');
JWT.sign(payload, merchantPrivateKey, { header: headers });
Piecing it all together: code sample for constructing and signing a JWT
Here’s a real-life example in Node.js that shows you how to construct and sign a JWT.
const JWT = require('jsonwebtoken');
const {createHash} = require('crypto');
const requestBody = {
"amount": 0,
"currency": "USD"
....
}
// Header
const kid = "03b941e3-3615-47a5-a046-766d5a4544e3."
const headers = { alg:'RS256', typ: 'JWT', kid};
// Payload
const iat = Math.floor(new Date().getTime() / 1000) - 1;
const exp = iat + 1200 - 1 // 1200 seconds = 20 minutes (max allowed range)
const requestUri = "/payments" // The URI path of the payment request you're invoking. Do NOT include query params.
const bodyToHash = requestBody ? JSON.stringify(requestBody) : '';
const hashed_request = createHash('sha512').update(`${requestUri}.${bodyToHash}`).digest('HEX');
const payload = { iat, exp, hashed_request };
//Sign the JWT
const merchantPrivateKey = "MIIJJwIBAAKCAgEArpQSiF6C5dDqAWIfR1fqkIeJb5YfoXWTx981KK1uF0YGNlWX...." // Your private key (this is an example; it is not recommended to store your private key in your source code)
JWT.sign(payload, merchantPrivateKey, { header: headers });
The signature returned by JWT.sign()
will look something like this:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleSBpZCByZWNpdmVkIGZyb20gaHViLCBraWQgbXVzdCBiZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNwZWNpZmljIGFwcCJ9.eyJpYXQiOjE2Nzg3ODI3MDAsImV4cCI6MTY3ODc4Mzg5OSwiaGFzaGVkUmVxdWVzdCI6IjM4NzRkOWIyY2U5MDljNWI3MzRmZDViOWZiZjI2OGIyNzliOTE5ZjkzZTcxZTM0ZjJmNDdmOWYzYjgxMjZhNDA2Y2Y0MjFlYmE3NTNjNTA3NDcwZTRlZmM2OTM2ZmFlM2IzN2Y1MWVhNGI1NTZmZGIxNDg2MzI4YzM0MmRmNTM0In0.Z8GrT2vYZDLQ18BjGPBJIpGAhC3DZyTmj9DByAmY-MXdcbxb_Y2erNgDlTuljAj3yFqIRcglYmFuvbPHTbFqxSguh7ayoNA0Lqw-nrdpE7LfqhetDivPI6QxrfNe8UTuaiPM-o4pYtleX1i8LNebOEEpBgd1lT...
Passing the JWT in the Header of the Payments API Requests
Now that you have the JWT, pass it in the authorization
field in the request header of the Payments API requests you invoke, like so:
x-payments-os-env: test
api-version: 1.3.0
app-id: com.mycomp.docapp
authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9....
idempotency-key: AGJ8FJLkGHIpHUTK
Rotating your Private and Public Key Pair
While PayU does not enforce key rotation, rotating your private and public key pair is a common security practice and we strongly recommend you rotate your keys periodically.
Rotating the key pair involves generating a new private-public key pair and replacing the existing key pair. After generating the new pair, simply assign the new public key to your account and to your business units. Note that the public key you wish to retire can remain assigned to your Business Units, pending the transition to the new public key; in this manner transactions initiated during the transition period will still be authenticated using your existing private-public key pair. After you complete your implementation for transitioning to the new key pair, simply remove the old public key from your Business Units.
Supported Payments API Endpoints
While you can use non-repudiation API authentication with most Payments API endpoints, you cannot use it with the Create a Token and Retrieve a Token API (you can only authenticate those request using using basic authentication with the Enterprise-platform API Keys). Likewise, you can only validate the integrity of Webhook data using basic authentication with the Enterprise-platform API Keys.