Skip to main content

Set Card PIN

This guides provides a detailed walkthrough on how to incorporate Basis Theory SDKs into your frontend for secure card PIN collection, and send them in accordance with regulations to the Issuer API through the Basis Theory Proxy.

Set Card PIN

Getting Started

To get started, you will need a Basis Theory account and a Tenant.

Provisioning Resources

In this section, we will explore the bare minimum resources necessary to enable your users to set PIN on their cards.

Management Application

To create all subsequent resources, you will need a Management Application.

Click here to create a Management Application or login to your Basis Theory account and create a new application with the following settings:

  • Name - Resource Creator
  • Application Type - Management
  • Permissions: application:create, proxy: create
Save the API Key from the created Management Application as it will be used later in this guide.

Public Application

You will need a Public Application to initialize Basis Theory Elements. This application represents your Frontend.

Using the Management Application key to authorize the request, call the Basis Theory API to create a new Public Application:

curl "https://api.basistheory.com/applications" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Frontend",
"type": "public"
}'

Observe that at this stage, no permissions have been allocated yet. They will be granted later programmatically using sessions.

Be sure to replace <MANAGEMENT_API_KEY> with the Management API Key you created previously.
Save the key from the created Public Application as it will be used later in this guide.

Private Application

To authorize the Proxy call to be performed by your Frontend, you will need a Private Application. This new application represents your Backend.

Using the Management Application key to authorize the request, call Basis Theory API to create a new Private Application:

curl "https://api.basistheory.com/applications" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Backend",
"type": "private",
"permissions": [ "token:use" ]
}'
Be sure to replace <MANAGEMENT_API_KEY> with the Management API Key you created previously.
Save the key from the created Private Application as it will be used later in this guide.

Pre-Configured Proxy

Now we will create a Pre-Configured Proxy that listens for incoming requests from the Frontend, injects credentials and forwards the PIN to the Issuer API. To achieve that, we will leverage a Request Transform code to manipulate the request as necessary:

Lithic expects an encrypted PIN Block in the pin field when updating a card. The code below receives the pin in plaintext, encrypts the PIN Block using Lithic's Public Key, and injects the necessary credentials to call the endpoint. See documentation.

requestTransform.js
const { Buffer } = require("buffer");
const forge = require("node-forge");

const createKey = (LITHIC_PUB_KEY) => {
const pem = Buffer.from(LITHIC_PUB_KEY, "base64").toString();
return forge.pki.publicKeyFromPem(pem);
};

function randomInt(low, high) {
// Generate random integer between low and high, inclusive
return Math.floor(Math.random() * (high - low + 1) + low);
}

const encryptPinBlock = (pin, publicKey) => {
const nonce = randomInt(1e8, 1e12);
const pinBlock = {
nonce,
pin,
};

const ciphertext = publicKey.encrypt(JSON.stringify(pinBlock), "RSA-OAEP", {
md: forge.md.sha1.create(),
mgf1: {
md: forge.md.sha1.create(),
},
});

return forge.util.encode64(ciphertext);
};

module.exports = (req) => {
const {
args: {
headers,
body
},
configuration: {
LITHIC_PUB_KEY,
LITHIC_API_KEY
}
} = req;
const { pin } = body;
const publicKey = createKey(LITHIC_PUB_KEY);
const pinBlock = encryptPinBlock(pin, publicKey);

return {
body: {
pin: pinBlock,
},
headers: {
...headers,
Authorization: LITHIC_API_KEY,
},
};
};

Let's store the contents of the requestTransform.js file into a variable:

request_transform_code=$(cat requestTransform.js)

And call the Basis Theory API to create the Proxy:

curl "https://api.basistheory.com/proxies" \
-X "POST" \
-H "BT-API-KEY: <MANAGEMENT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "PIN Proxy",
"destination_url": "https://sandbox.lithic.com/v1/cards",
"request_transform": {
"code": '"$(echo $request_transform_code | jq -Rsa .)"'
}
}'
Be sure to replace <MANAGEMENT_API_KEY> with the Management API Key you created previously.
Save the key from the created Proxy as it will be used later to invoke it.

Done! These are all the resources needed. Let's see how to use them.

Configuring Basis Theory Elements

Basis Theory Elements are available for the following technologies. Click below for detailed instructions on how to install and configure them.

Javascript
React
React Native
iOS
Android

Adding Text Element

Once installed and configured, add the Text Element to your application. This will enable your users to type in their desired card PIN in your form, while ensuring your systems never come in contact with the data.

<div id="pin"></div>
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let pinElement;

async function init() {
bt = await new BasisTheory().init('<PUBLIC_API_KEY>', { elements: true });

const wrapper = document.getElementById("pin");
wrapper.style.border = "1px solid black";

pinElement = bt.createElement("text", {
targetId: "pinElement",
mask: [/\d/u, /\d/u, /\d/u, /\d/u],
style: {
base: {
fontSize: "25px",
fontFamily: "conceal",
textAlign: "center"
}
}
});

await pinElement.mount(wrapper);
}

init();

Setting the PIN

With the TextElement enabling you to securely capture the PIN in your user-facing application(s), it is time to send it to the Issuer API using the Proxy. We will add a button to trigger a function to get the job done.

Creating a Session

The first step of securely fetching the card data is creating a Session to grant temporary elevated access to our Public Application. To create a session, add the following code:

<div id="pin"></div>
<button onclick="submit();">Submit</button>
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let pinElement;

async function init () { ... }

async function submit () {
try {
const session = await bt.sessions.create();
} catch (error) {
console.error(error);
}
}

init();
Be sure to replace <PUBLIC_API_KEY> with the Public API Key you created previously.

Authorizing a Session

Sessions must be authorized by a Private Application to perform any actions against backend resources, such as the Proxy. In our case, we want to allow it to invoke the Proxy, which requires token:use permission.

We will add a new /authorize endpoint to our backend that receives the session nonce and authorizes it with the necessary permissions.

In this example, we are using Basis Theory SDK and Express framework for Node.js.

const express = require("express");
const cors = require("cors");
const { BasisTheory } = require("@basis-theory/basis-theory-js");

const app = express();
const port = 4242;

app.use(cors());
app.use(express.json());

app.post("/authorize", async (request, response) => {
const bt = await new BasisTheory().init("<PRIVATE_API_KEY>");
const { nonce } = request.body;
// authorizing a session returns an empty 200 response
await bt.sessions.authorize({
nonce: nonce,
permissions: ['token:use']
});
response.status(204).send();
});

app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
On a production environment, your endpoint to authorize session should be behind your own authorization scheme.
Be sure to replace <PRIVATE_API_KEY> with the Private API Key you created previously.

Now let's have our frontend submit function call this new endpoint passing the session nonce.

import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let pinElement;

async function init () { ... }

async function submit () {
try {
const session = await bt.sessions.create();
await fetch('http://localhost:4242/authorize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ nonce: session.nonce }),
});
} catch (error) {
console.error(error);
}
}

init();

Invoking the Proxy

After properly authorizing the user session, you can use it to invoke the Pre-Configured Proxy by passing the Text Element value for the pin attribute in the request body.

Additionally, we pass the Lithic's card id in the request path to properly call the Update Card endpoint.

import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let pinElement;

async function init () { ... }

async function submit () {
try {
const session = await bt.sessions.create();
await fetch('http://localhost:4242/authorize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ nonce: session.nonce }),
});
await bt.proxy.patch({
path: "ef7d65c-9023-4da3-b113-3b8583fd7951",
headers: {
"BT-PROXY-KEY": "proxy_key_1234567890"
},
body: {
pin: pinElement,
},
apiKey: session.sessionKey
});
} catch (error) {
console.error(error);
}
}

init();
Be sure to replace proxy_key_1234567890 with the Proxy Key you created previously.

And that's it. Invoking the proxy is the last step to securely set the card PIN against the issuer API.

Conclusion

This comprehensive guide equips developers with the essential tools to seamlessly integrate Basis Theory's frontend SDKs for secure card PIN capture. By diligently following the outlined steps, developers ensure both robust security and adherence to PCI compliance, while unlocking the possibilities around Elements customization.

Our emphasis on security, flexibility and customization not only addresses inherent challenges in card PIN handling but also paves the way for innovative solutions in collaboration with card issuers. With code samples spanning various frontend and backend technologies, developers gain the knowledge needed to navigate diverse development environments effectively. By adopting these practices, developers bolster their ability to create secure, adaptable and customized solutions that resonate within the dynamic landscape of modern payment systems.

Learn More