Query User Data from an API
This blueprint provides a guide to securely share Personal Identifiable Information (PII) with Clients querying your Server API, without having to store sensitive data on your own database.
We will use Terraform to provision Basis Theory resources and https://echo.basistheory.com
to represent the API. If you are not using Terraform, you can call Basis Theory APIs directly.
Don't want to complete this guide? View the completed example project here.
Creating the Inbound Proxy
In order to listen to API requests, we will create a Proxy that sits between the Client and your Server. We will assume these requests don't contain any sensitive information, so we will pass them through to the Server, which will respond with tokenized data. The Proxy will then transform the Server response to detokenize it and respond to the Client with the plaintext data.
Create a Management Application
Management Applications are used to manage Basis Theory Tenants settings and services. We will create a new Application and pass its key to the Terraform provider. If you want to make direct API calls for these purposes, you can use that application key to authenticate your requests.
Click here to create one. This will create an application capable of managing Applications and Proxies, using the following Access Controls:
- Permissions:
application:*
,proxy:*
Save the API Key from the created Management Application as it will be used later in the next step.
Install Basis Theory Terraform Provider
Add basistheory
provider to your Terraform configuration:
terraform {
required_providers {
basistheory = {
source = "Basis-Theory/basistheory"
version = ">= 0.7.0"
}
}
}
variable "management_api_key" {}
provider "basistheory" {
api_key = var.management_api_key
}
Paste the API key to your terraform.tfvars
file:
management_api_key = "test_1234567890"
Initialize Terraform:
terraform init
Add Terraform Resources
Now that we have Terraform set up, let's add the required resources to start proxying requests to our Server.
Private Application Resource
We need a Private Application to grant the right Access Controls to the Proxy, so it can interact with our tokens:
- Permissions:
token:read
- Containers:
/pii/high/
- Transform:
reveal
Add the Private Application resource to your Terraform configuration, or invoke the API directly:
- Terraform
- cURL
resource "basistheory_application" "proxy_application" {
name = "Proxy Application"
type = "private"
rule {
description = "Read PII Tokens"
priority = 1
container = "/pii/high/"
transform = "reveal"
permissions = [
"token:read",
]
}
}
curl "https://api.basistheory.com/applications" \
-X "POST" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "BT-API-KEY: test_1234567890" \
-d '{
"name": "Proxy Application",
"type": "private",
"rules": [
{
"container": "/pii/high/",
"description": "Read PII Tokens",
"permissions": [
"token:read"
],
"priority": 1,
"transform": "reveal"
}
]
}'
Proxy Resource
Next, we will create a Proxy capable of passing Client requests to a certain destination URL (Server), listen to the response and detokenize part of its body
, and respond with the transformed content back to the Client.
Create a detokenize.js
file with the following code:
const {
BadRequestError,
} = require("@basis-theory/basis-theory-reactor-formulas-sdk-js");
module.exports = async function (req) {
const { bt, args } = req;
const { body, headers } = args;
try {
const { json } = body; // format returned by echo Server
const { ssn } = json; // look for the token to detokenize
const token = await bt.tokens.retrieve(ssn); // detokenization step
return {
body: { // transformed body
ssn: token.data, // replace token id with plaintext data
},
headers, // original headers are not altered
};
} catch (e) {
if (typeof e.status === "number") {
// if there is a status code in the error
// throwing this exception will cause Client to receive a 400
throw new BadRequestError(e);
}
// otherwise, serialize the error back to the requester
return {
body: {
error: JSON.stringify(e)
},
headers,
};
}
};
Now let's add our Proxy resource to Terraform configuration:
- Terraform
- cURL
resource "basistheory_proxy" "inbound_proxy" {
name = "Inbound Proxy"
destination_url = "https://echo.basistheory.com/anything"
application_id = basistheory_application.proxy_application.id
response_transform = {
code = file("./detokenize.js")
}
require_auth = false
}
output "inbound_proxy_key" {
value = basistheory_proxy.inbound_proxy.key
description = "Inbound Proxy Key"
sensitive = true
}
curl "https://api.basistheory.com/proxies" \
-X "POST" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "BT-API-KEY: test_1234567890" \
-d '{
"name": "Inbound Proxy",
"destination_url": "https://echo.basistheory.com/anything",
"application_id": "",
"response_transform": {
"code": "const {\n BadRequestError,\n} = require(\"@basis-theory/basis-theory-reactor-formulas-sdk-js\");\n\nmodule.exports = async function (req) {\n const { bt, args } = req;\n const { body, headers } = args;\n\n try {\n const { json } = body; // format returned by echo Server\n const { ssn } = json; // look for the token to detokenize\n const token = await bt.tokens.retrieve(ssn); // detokenization step\n return {\n body: { // transformed body\n ssn: token.data, // replace token id with plaintext data\n },\n headers, // original headers are not altered\n };\n } catch (e) {\n if (typeof e.status === \"number\") {\n // if there is a status code in the error\n // throwing this exception will cause Client to receive a 400\n throw new BadRequestError(e);\n }\n // otherwise, serialize the error back to the requester\n return {\n body: {\n error: JSON.stringify(e)\n },\n headers,\n };\n }\n};"
},
"require_auth": false
}'
A few things to notice:
- The
name
attribute is arbitrary - something to help us track the nature of the resource; - The value of
destination_url
can be any API endpoint; - The Private Application specified before is linked to the proxy using
application_id
. It'skey
is used to injectreq.bt
, an instance of Basis Theory Node.js SDK; - Passing
require_auth = false
allows any Client to invoke the Proxy without using Basis Theory Authentication - meaning that Client AuthN/AuthZ should be handled by the Server.
Finally, instruct Terraform to create the resources:
terraform apply
Save the inbound_proxy_key
output for later invoking the Proxy.
Using the Inbound Proxy
Our scenario is a typical Inbound Detokenization setup. This means that by the time the Client queries the Server for User Data, it should exist already in the form of tokens. In the following steps, we will create a Social Security Number token and use the Proxy to retrieve its value.
Create a Token
There are many ways of collecting data with Basis Theory. Since our goal is to share data using the Proxy, we will take the easy route to create new tokens.
Public Application
To securely create PII tokens, you'll need a Public Application using our template Collect PII Data
. Click here to create one.
This will create a application with the following Access Controls:
- Permissions:
token:create
,token:update
- Containers:
/pii/
- Transform:
mask
Save the API Key from the created Public Application as it will be used in the next step.
Tokenization
Call the Create Token token endpoint, using the key from the previous step:
curl "https://api.basistheory.com/tokens" \
-X "POST" \
-H "BT-API-KEY: test_1234567890" \
-H "Content-Type: application/json" \
-d '{
"type": "social_security_number",
"data": "123456789"
}'
You should see a JSON response similar to:
{
"id": "f4867f40-a772-44d4-937a-1bb8c6dd322b",
"type": "social_security_number",
"tenant_id": "88b4fab9-eb9c-494f-a8fa-9e4847eac788",
"data": "XXXXX6789",
"created_by": "8b8fbf07-2f1e-4b45-bf0f-f2e651b112e0",
"created_at": "2022-12-15T13:09:25.9869097+00:00",
"fingerprint": "JE2WHCSeKCZUF5WMVavMZSvqQ61xf2zhCpYMieLoz9AD",
"fingerprint_expression": "{{ data | remove: '-' }}",
"mask": "{{ data | reveal_last: 4 }}",
"privacy": {
"classification": "pii",
"impact_level": "high",
"restriction_policy": "mask"
},
"search_indexes": [
"{{ data }}",
"{{ data | replace: '-' }}",
"{{ data | last4 }}"
],
"containers": [
"/pii/high/"
]
}
Copy the id
to use in the next step.
Invoking the Proxy
Now it is time to finally test the Proxy with our recently created token. We will use the token id
as part of our request payload, so it is echoed back from our destination Server and detokenized at the Proxy response transform code.
In a real world scenario, the data used to query the User Data may be different, such as the User id stored in the Server database.
Invoke the Proxy using the inbound_proxy_key
created previously:
curl "https://api.basistheory.com/proxy?bt-proxy-key=inbound_proxy_key" \
-X "POST" \
-H "Content-Type: application/json" \
-d '{
"ssn": "f4867f40-a772-44d4-937a-1bb8c6dd322b"
}'
You should see a JSON response like:
{
"ssn": "123456789"
}
What is Happening?
A secure HTTPS request is made to Basis Theory's Proxy endpoint. Basis Theory forwards the request to the specified destination URL, the Echo Server. Once the Server responds with a full echo body, the Proxy's transform response code looks for the token id
to detokenize under req.args.body.json.ssn
path. The Proxy detokenizes the SSN by retrieving the token data
and passing it in the response body to the Client.
This allows you to share sensitive PII data to any Client, without touching the data and, therefore, keeping your systems out of PII regulation scope.
Conclusion
Following this Blueprint enables you to meet regulatory requirements for storing and sharing sensitive PII by using Tokenization. This approach reliefs the burden of having to implement your own data encryption to protect customers privacy.
Have feedback or questions? Join us in our Slack community.