Skip to main content

Display Cards

Displaying cards to your application users is a feature that often comes with its own set of problems. Being able to implement a custom and refined user experience usually means either having to deal with the compliance burden associated with handling the sensitive cardholder data directly, or having to compromise aesthetics and developer experience trying to conform with outworn solutions.

In this guide we will cover the necessary steps to integrate your frontend with Basis Theory SDKs to display tokenized cardholder data to your users, while keeping security control in your backend. The visual Elements are focused in the card data points: primary account number (PAN), expiration date and verification code, allowing you own the card visuals, which is out of scope for this guide.

Display Cards

Unlock a seamless experience with Basis Theory by ensuring you have card tokens readily available before diving into the integration steps. The guides below can walk you through how to start protecting cardholder data with the platform:

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 display cards to your users.

Public Application

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

Click here to create it. Notice how no permissions are assigned at this point. They will be programmatically granted later using sessions.

Save the API Key from the created Public Application as it will be used later in this guide.

Private Application

Next, you will need a Private Application using our PCI-compliant template Read PCI Tokens.

Click here to create it with the following Access Controls:

  • Permissions: token:read
  • Containers: /pci/
  • Transform: mask
Save the API Key from the created Public Application as it will be used later in this guide.

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
iOS
Android

Adding Card Elements

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

index.html
<div id="cardNumber"></div>
<div style="display: flex;">
<div id="cardExpirationDate" style="width: 100%;"></div>
<div id="cardVerificationCode" style="width: 100%;"></div>
</div>
index.js
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;

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

// Creates Elements instances
cardNumberElement = bt.createElement('cardNumber', {
targetId: 'myCardNumber' // (custom) used for tracking validation errors
});
cardExpirationDateElement = bt.createElement('cardExpirationDate', {
targetId: 'myCardExpiration'
});
cardVerificationCodeElement = bt.createElement('cardVerificationCode', {
targetId: 'myCardVerification'
});

// Mounts Elements in the DOM in parallel
await Promise.all([
cardNumberElement.mount('#cardNumber'),
cardExpirationDateElement.mount('#cardExpirationDate'),
cardVerificationCodeElement.mount('#cardVerificationCode'),
]);
}

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

Displaying Card Data

Now that we have Elements ready, it is time to fetch the card data and display it. We will add a button to trigger a function that gets 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. Add the following code to create a session:

index.html
<div id="cardNumber"></div>
<div style="display: flex;">
<div id="cardExpirationDate" style="width: 100%;"></div>
<div id="cardVerificationCode" style="width: 100%;"></div>
</div>
<button onclick="display();">Display</button>
index.js
import { BasisTheory } from '@basis-theory/basis-theory-js';

let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;

async function init () { ... }

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

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

Authorizing a Session

Sessions must be authorized by a Private Application to perform any actions against tokens. In our case, we want to allow it to temporarily read the card token to display it for the user.

For example, given you have a previously stored card token with the following identifier:

token.json
{
"id": "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
"type": "card",
"tenant_id": "15f48eb5-8b52-4cdd-a396-608f7cf001d0",
"data": {
"number": "XXXXXXXXXXXX4242",
"expiration_month": 12,
"expiration_year": 2025
},
"created_by": "4a6ae2a6-79f8-4640-968f-88db913743df",
"created_at": "2023-04-17T12:54:44.8320458+00:00",
"mask": {
"number": "{{ data.number | reveal_last: 4 }}",
"expiration_month": "{{ data.expiration_month }}",
"expiration_year": "{{ data.expiration_year }}"
},
"privacy": {
"classification": "pci",
"impact_level": "high",
"restriction_policy": "mask"
},
"search_indexes": [],
"containers": [
"/pci/high/"
]
}

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("test_priv_1234567890");
const { nonce } = request.body;
// authorizing a session returns an empty 200 response
await bt.sessions.authorize({
nonce: nonce,
rules: [
{
description: "Display Token",
priority: 1,
conditions: [
{
attribute: "id",
operator: "equals",
value: "d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4",
},
],
permissions: ["token:read"],
transform: "reveal",
},
],
});
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 test_priv_1234567890 with the Private API Key you created previously.

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

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

let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;

async function init () { ... }

async function display () {
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();

Retrieving the Card Token

With the frontend session properly authorized, it is time to retrieve the token using the session's key and display the card data to the user.

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

let bt;
let cardNumberElement;
let cardExpirationDateElement;
let cardVerificationCodeElement;

async function init () { ... }

async function display () {
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 }),
});
const token = await bt.tokens.retrieve('d2cbc1b4-5c3a-45a3-9ee2-392a1c475ab4', {
apiKey: session.sessionKey
});
cardNumberElement.setValue(token.data.number);
cardExpirationDateElement.setValue({
year: token.data.expiration_year,
month: token.data.expiration_month,
});
cardVerificationCodeElement.setValue(token.data.cvc);
} catch (error) {
console.error(error);
}
}

init();

In the steps above, we had retrieved the card token data within the secure Elements context. This means we can set Elements value, and therefore display it, but without interacting with the data directly.

Key Considerations

Design

Basis Theory Elements' unopinionated nature allows for creative freedom, enabling your design team to craft a visually appealing interface that aligns with the brand's identity and resonates with the target audience. Applying good design principles within a familiar developer experience ensures that your business is not restricted to antiquated ways of solving the compliance problem.

CVC Retention

Card tokens retain verification code for 1 hour. This limitation is enforced by default to comply with PCI-DSS guidelines. However, depending on your business case, extending this period or implementing technical workarounds may be possible upon analysis. Please, reach out to our support team for assistance anytime.

Security

Data security and compliance are major concerns when dealing with cardholder data. By using the Application Templates provided in the Provisioning Resources section, you can rest assured that your applications won't directly access sensitive data at any given moment.

To display the cardholder to the end user, the temporary frontend session is briefly granted escalated privilege of reading unmasked tokenized data, while the secure Elements context is used for making the HTTPS request and manipulating the data.

Conclusion

The best practices prescribed in this guide ensure that your applications stay compliant with PCI-DSS standards while displaying cards to your users. With the access controls provisioned in the steps above, we are authorizing the frontend to retrieve existing tokenized data from the secure vault and display it using minimal UI components.

While the example workflow is triggered by user interaction through a button click, your team can design different user experiences that better fit your app. You may chose to do all the steps when the view loads; or perhaps create a session earlier in the workflow, and only authorize if/when displaying the data is necessary.

Learn More