Authenticate with 3DS
3DS (3D Secure) is an online payment authentication protocol that enhances anti-fraud efforts. It requires cardholders to undergo an additional layer of verification, such as a one-time password or biometric scan, during online transactions. It is also the most common implementation of Secure Customer Authentication (SCA) for regulations such as PSD2.
In this guide, we will set up the Basis Theory 3DS SDK to start a 3DS transaction in a frontend web application, and securely authenticate it using the Basis Theory Platform.
Getting Started
To get started, you will need to create a Basis Theory Account and a TEST 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 with permissions to create tokens and 3DS sessions. Click here to create one.
This will create an application with the following Permissions:
- Permissions:
token:create
,3ds:session:create
Private Application
Next, you will need a Private Application for your backend with the permission to authenticate 3DS sessions.
Click here to create it with the following Permissions:
- Permissions:
3ds:session:authenticate
Configuring the Basis Theory 3DS Web SDK
The 3DS Web SDK is available as an NPM package.
See instructions below on how to install and configure it.
Creating a Card Token
In order to run 3DS authentication on a customer card, it must be first tokenized with Basis Theory. Follow the Collect Cards Guide to learn how to create a card token using a variety of different technologies available through the Basis Theory SDKs.
Creating a Session and Device Fingerprinting
The 3D Secure process starts with identifying certain information about the customer's device to aid in verifying if the transaction is valid. The Basis Theory 3DS SDK takes care of that process, collecting all necessary information and sending that over to the 3D secure server for processing.
First, let's create a 3DS session
and pass the created card token id
as the pan
property.
The SDK will then collect the device info as previously described, and send it alongside the de-tokenized card number (pan) to the 3DS server in order to initiate a 3DS transaction.
import { BasisTheory3ds } from "@basis-theory/3ds-web";
const checkout = async () => {
// initializing the 3ds sdk
const bt3ds = BasisTheory3ds("<PUBLIC_API_KEY>");
// creating the session
const session = await bt3ds.createSession({ pan: "<CARD_TOKEN_ID>" });
}
<PUBLIC_API_KEY>
with the Public API Key you created in the Public Application step,
and <CARD_TOKEN_ID>
with the token id created in the Creating a Card Token step.Authenticating a Session
Once the session
is created, it must be authenticated.
In this process, the merchant or requestor must send information about the transaction to the 3DS server.
This is done by calling Authenticate 3DS Session endpoint from your own backend, with the private API key created earlier.
- Node
- .NET
- Python
- Go
In this example, we are using Basis Theory SDK and Express framework for Node.js.
const { BasisTheory } = require("@basis-theory/basis-theory-js");
const express = require("express");
const app = express();
const PORT = 3000;
app.use(express.json());
let bt;
(async () => {
bt = await new BasisTheory().init("<API_KEY>");
})();
app.post("/:sessionId/authenticate", async (req, res) => {
const { sessionId } = req.params;
try {
const authentication = await bt.threeds.authenticateSession(sessionId, {
authenticationCategory: "payment",
authenticationType: "payment-transaction",
purchaseInfo: {
amount: "80000",
currency: "826",
exponent: "2",
date: "20240109141010"
},
requestorInfo: {
id: "example-3ds-merchant",
name: "Example 3DS Merchant",
url: "https://www.example.com/example-merchant"
},
merchantInfo: {
mid: "9876543210001",
acquirerBin: "000000999",
name: "Example 3DS Merchant",
categoryCode: "7922",
countryCode: "826"
},
cardholderInfo: {
name: "John Doe",
email: "john@me.com"
}
});
res.status(200).send(authentication);
} catch (error) {
console.error('Error during authentication:', error);
res.status(500).send({ error: "Internal Server Error" });
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
In this example, we are using Basis Theory SDK and ASP.NET Core Framework.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
using BasisTheory.net.ThreeDS;
using BasisTheory.net.ThreeDS.Requests;
namespace server.Controllers
{
public class Program
{
public static void Main(string[] args)
{
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://0.0.0.0:4242")
.UseWebRoot("public")
.UseStartup<Startup>()
.Build()
.Run();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseStaticFiles();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
[ApiController]
public class ThreeDsApiController : Controller
{
private readonly ThreeDSClient _client;
public ThreeDsApiController()
{
_client = new ThreeDSClient("<PRIVATE_API_KEY>");
}
[HttpPost("{sessionId:guid}/authenticate")]
public async Task<ActionResult> AuthenticateSession([FromRoute] Guid sessionId)
{
var authentication = await _client.AuthenticateThreeDSSessionAsync(sessionId.ToString(), new AuthenticateThreeDSSessionRequest
{
AuthenticationCategory = "payment",
AuthenticationType = "payment-transaction",
PurchaseInfo = new ThreeDSPurchaseInfo
{
Amount = "80000",
Currency = "826",
Exponent = "2",
Date = "20240109141010"
},
RequestorInfo = new ThreeDSRequestorInfo
{
Id = "example-3ds-merchant",
Name = "Example 3DS Merchant",
Url = "https://www.example.com/example-merchant"
},
MerchantInfo = new ThreeDSMerchantInfo
{
Mid = "9876543210001",
AcquirerBin = "000000999",
Name = "Example 3DS Merchant",
CategoryCode = "7922",
CountryCode = "826"
},
CardholderInfo = new ThreeDSCardholderInfo
{
Name = "John Doe",
Email = "john@me.com"
}
});
if (authentication == null)
{
return BadRequest("Failed to authenticate session.");
}
return Ok(authentication);
}
}
}
In this example, we are using Basis Theory SDK and Flask Framework.
import os
from flask import Flask, request, jsonify
import basistheory
from basistheory.api import three_ds_api
from basistheory.model import AuthenticateThreeDSSessionRequest, ThreeDSPurchaseInfo, ThreeDSRequestorInfo, ThreeDSMerchantInfo, ThreeDSCardholderInfo
app = Flask(__name__)
@app.route('/<sessionId>/authenticate', methods=['POST'])
def authenticate_session(sessionId):
config = basistheory.Configuration(
host="https://api.basistheory.com",
api_key="<PRIVATE_API_KEY>"
)
with basistheory.ApiClient(configuration=config) as api_client:
three_ds_client = three_ds_api.ThreeDSApi(api_client)
authentication_request = AuthenticateThreeDSSessionRequest(
authentication_category="payment",
authentication_type="payment-transaction",
purchase_info=ThreeDSPurchaseInfo(
amount="80000",
currency="826",
exponent="2",
date="20240109141010"
),
requestor_info=ThreeDSRequestorInfo(
id="example-3ds-merchant",
name="Example 3DS Merchant",
url="https://www.example.com/example-merchant"
),
merchant_info=ThreeDSMerchantInfo(
mid="9876543210001",
acquirer_bin="000000999",
name="Example 3DS Merchant",
category_code="7922",
country_code="826"
),
cardholder_info=ThreeDSCardholderInfo(
name="John Doe",
email="john@me.com"
)
)
authentication = three_ds_client.three_ds_authenticate_session(sessionId, authenticate_three_ds_session_request=authentication_request)
return jsonify(authentication.to_dict()), 200
if __name__ == '__main__':
app.run(port=4242, debug=True)
In this example, we are using Basis Theory SDK, Go HTTP package and the Gorilla Mux Router.
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"github.com/Basis-Theory/basistheory-go/v5"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/{sessionId}/authenticate", authenticateSession).Methods("POST")
addr := "localhost:4242"
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, router))
}
func authenticateSession(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionId := vars["sessionId"]
configuration := basistheory.NewConfiguration()
apiClient := basistheory.NewAPIClient(configuration)
contextWithAPIKey := context.WithValue(context.Background(), basistheory.ContextAPIKeys, map[string]basistheory.APIKey{
"ApiKey": {Key: "<PRIVATE_API_KEY>"},
})
authenticateThreeDSSessionRequest := *basistheory.NewAuthenticateThreeDSSessionRequest()
authenticateThreeDSSessionRequest.SetAuthenticationCategory("payment")
authenticateThreeDSSessionRequest.SetAuthenticationType("payment-transaction")
purchaseInfo := *basistheory.NewThreeDSPurchaseInfo()
purchaseInfo.SetAmount("80000")
purchaseInfo.SetCurrency("826")
purchaseInfo.SetExponent("2")
purchaseInfo.SetDate("20240109141010")
requestorInfo := *basistheory.NewThreeDSRequestorInfo()
requestorInfo.SetId("example-3ds-merchant")
requestorInfo.SetName("Example 3DS Merchant")
requestorInfo.SetUrl("https://www.example.com/example-merchant")
merchantInfo := *basistheory.NewThreeDSMerchantInfo()
merchantInfo.SetMid("9876543210001")
merchantInfo.SetAcquirerBin("000000999")
merchantInfo.SetName("Example 3DS Merchant")
merchantInfo.SetCategoryCode("7922")
merchantInfo.SetCountryCode("826")
cardholderInfo := *basistheory.NewThreeDSCardholderInfo()
cardholderInfo.SetName("John Doe")
cardholderInfo.SetEmail("john@me.com")
authenticateThreeDSSessionRequest.SetPurchaseInfo(purchaseInfo)
authenticateThreeDSSessionRequest.SetRequestorInfo(requestorInfo)
authenticateThreeDSSessionRequest.SetMerchantInfo(merchantInfo)
authenticateThreeDSSessionRequest.SetCardholderInfo(cardholderInfo)
authenticateResponse, _, authenticateErr := apiClient.ThreeDSApi.ThreeDSAuthenticateSession(contextWithAPIKey, sessionId).AuthenticateThreeDSSessionRequest(authenticateThreeDSSessionRequest).Execute()
if authenticateErr != nil {
rw.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(rw).Encode(map[string]string{"error": authenticateErr.Error()})
return
}
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(authenticateResponse)
}
<PRIVATE_API_KEY>
with the Private API Key you created previously.And then passing that information back to your frontend.
import { BasisTheory3ds } from "@basis-theory/3ds-web";
const checkout = async () => {
// initializing the 3ds sdk
const bt3ds = BasisTheory3ds("<PUBLIC_API_KEY>");
// creating the session
const session = await bt3ds.createSession({ pan: "<CARD_TOKEN_ID>" });
// authenticating the session
const authResponse = await fetch(
`{your-backend-host}/${session.id}/authenticate`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
const authentication = await authResponse.json();
};
If the status
for the authentication response is successful
, that means a frictionless authentication happened and the authentication token is available as the authentication_value
property.
Performing a Challenge
If after authenticating a 3DS session, the authentication response status
is set as challenge
, that means that the customer needs to perform a challenge, like inputting a passcode, before getting the final 3DS authentication value.
You can trigger the user challenge by calling the startChallenge
method, which returns a Promise
that will only be resolved once the user completes it.
import { BasisTheory3ds } from "@basis-theory/3ds-web";
const checkout = async () => {
// initializing the 3ds sdk
const bt3ds = BasisTheory3ds("<PUBLIC_API_KEY>");
// creating the session
const session = await bt3ds.createSession({ pan: "<CARD_TOKEN_ID>" });
// authenticating the session
const authResponse = await fetch(
`{your-backend-host}/${session.id}/authenticate`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
const authentication = await authResponse.json();
if (authentication.status === "challenge") {
// some information about the authentication is necessary to start a challenge
const challengeInfo = {
acsChallengeUrl: authentication.acs_challenge_url,
acsTransactionId: authentication.acs_transaction_id,
sessionId: session.id,
threeDSVersion: authentication.threeds_version,
};
// starting a challenge
// the response is just an object w/ the session id: { id: sessionId }
const challengeCompleted = await bt3ds.startChallenge(challengeInfo);
}
};
Styling the Challenge Window
The internal challenge 'window' is created and handled by card issuer and unfortunately there is not much that can be done to style it to fit with the design of your website.
What is possible though, is determining how big that window can be, and style the container that holds it.
Changing the Window Size
To change the window size, use the windowSize
property on the challenge request, passing along the code for the desired pre-defined size according to the table below.
const challengeInfo = {
//... other challenge props
windowSize = '01'
};
const challengeCompleted = await basisTheory3ds.startChallenge(challengeInfo);
By default, the challenge window size is set to 03
or 500px x 600px
WindowSize ID | Size |
---|---|
01 | 250px x 400px |
02 | 390px x 400px |
03 | 500px x 600px |
04 | 600px x 400px |
05 | 100% x 100% |
Style the Window Container
The challenge window is an iframe
that gets loaded inside a predefined container div
.
The container div always has a default id - challengeFrameContainer
, and that can be used to style it:
#challengeFrameContainer {
display: none;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5)
}
Using your own Window Container
If you want more flexibility in styling, like being able to embed the window on your own content or not having to rely on absolute
positioning, you can use your own container for the challenge window.
This can be done by passing the id
for your desired container in the challengeContainerOptions
object during initialization:
import { BasisTheory3ds } from "@basis-theory/3ds-web";
const bt3ds = BasisTheory3ds("<PUBLIC_API_KEY>", {
challengeContainerOptions: { id: "your_container_id" },
});
Retrieving a Challenge Result
Once a challenge is complete, results are retrieved by calling the Get Challenge Result endpoint from your backend.
This is done by calling the Basis Theory backend endpoint /3ds/{sessionId}/sessions/challenge-result
from your own backend, using the same private API key that was used to authenticate.
- Node
- .NET
- Python
- Go
In this example, we are using Basis Theory SDK and Express framework for Node.js.
const { BasisTheory } = require("@basis-theory/basis-theory-js");
const express = require("express");
const app = express();
const PORT = 3000;
app.use(express.json());
let bt;
(async () => {
bt = await new BasisTheory().init("<API_KEY>");
})();
app.post("/:sessionId/authenticate", async (req, res) => {
const { sessionId } = req.params;
try {
const authentication = await bt.threeds.authenticateSession(sessionId, {
authenticationCategory: "payment",
authenticationType: "payment-transaction",
purchaseInfo: {
amount: "80000",
currency: "826",
exponent: "2",
date: "20240109141010"
},
requestorInfo: {
id: "example-3ds-merchant",
name: "Example 3DS Merchant",
url: "https://www.example.com/example-merchant"
},
merchantInfo: {
mid: "9876543210001",
acquirerBin: "000000999",
name: "Example 3DS Merchant",
categoryCode: "7922",
countryCode: "826"
},
cardholderInfo: {
name: "John Doe",
email: "john@me.com"
}
});
res.status(200).send(authentication);
} catch (error) {
console.error('Error during authentication:', error);
res.status(500).send({ error: "Internal Server Error" });
}
});
app.get("/:sessionId/challenge-result", async (req, res) => {
const { sessionId } = req.params;
try {
const result = await bt.threeds.getChallengeResult(sessionId);
res.status(200).send(result);
} catch (error) {
console.error('Error getting challenge result:', error);
res.status(500).send({ error: "Internal Server Error" });
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
In this example, we are using Basis Theory SDK and ASP.NET Core Framework.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
using BasisTheory.net.ThreeDS;
using BasisTheory.net.ThreeDS.Requests;
namespace server.Controllers
{
public class Program
{
public static void Main(string[] args)
{
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://0.0.0.0:4242")
.UseWebRoot("public")
.UseStartup<Startup>()
.Build()
.Run();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
app.UseRouting();
app.UseStaticFiles();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
[ApiController]
public class ThreeDsApiController : Controller
{
private readonly ThreeDSClient _client;
public ThreeDsApiController()
{
_client = new ThreeDSClient("<PRIVATE_API_KEY>");
}
[HttpPost("{sessionId:guid}/authenticate")]
public async Task<ActionResult> AuthenticateSession([FromRoute] Guid sessionId)
{
var authentication = await _client.AuthenticateThreeDSSessionAsync(sessionId.ToString(), new AuthenticateThreeDSSessionRequest
{
AuthenticationCategory = "payment",
AuthenticationType = "payment-transaction",
PurchaseInfo = new ThreeDSPurchaseInfo
{
Amount = "80000",
Currency = "826",
Exponent = "2",
Date = "20240109141010"
},
RequestorInfo = new ThreeDSRequestorInfo
{
Id = "example-3ds-merchant",
Name = "Example 3DS Merchant",
Url = "https://www.example.com/example-merchant"
},
MerchantInfo = new ThreeDSMerchantInfo
{
Mid = "9876543210001",
AcquirerBin = "000000999",
Name = "Example 3DS Merchant",
CategoryCode = "7922",
CountryCode = "826"
},
CardholderInfo = new ThreeDSCardholderInfo
{
Name = "John Doe",
Email = "john@me.com"
}
});
if (authentication == null)
{
return BadRequest("Failed to authenticate session.");
}
return Ok(authentication);
}
[HttpGet("{sessionId:guid}/challenge-result")]
public async Task<ActionResult> GetChallengeResult([FromRoute] Guid sessionId)
{
var result = await _client.GetChallengeResultAsync(sessionId.ToString());
if (result == null)
{
return BadRequest("Failed to get challenge result.");
}
return Ok(result);
}
}
}
In this example, we are using Basis Theory SDK and Flask Framework.
import os
from flask import Flask, request, jsonify
import basistheory
from basistheory.api import three_ds_api
from basistheory.model import AuthenticateThreeDSSessionRequest, ThreeDSPurchaseInfo, ThreeDSRequestorInfo, ThreeDSMerchantInfo, ThreeDSCardholderInfo
app = Flask(__name__)
@app.route('/<sessionId>/authenticate', methods=['POST'])
def authenticate_session(sessionId):
config = basistheory.Configuration(
host="https://api.basistheory.com",
api_key="<PRIVATE_API_KEY>"
)
with basistheory.ApiClient(configuration=config) as api_client:
three_ds_client = three_ds_api.ThreeDSApi(api_client)
authentication_request = AuthenticateThreeDSSessionRequest(
authentication_category="payment",
authentication_type="payment-transaction",
purchase_info=ThreeDSPurchaseInfo(
amount="80000",
currency="826",
exponent="2",
date="20240109141010"
),
requestor_info=ThreeDSRequestorInfo(
id="example-3ds-merchant",
name="Example 3DS Merchant",
url="https://www.example.com/example-merchant"
),
merchant_info=ThreeDSMerchantInfo(
mid="9876543210001",
acquirer_bin="000000999",
name="Example 3DS Merchant",
category_code="7922",
country_code="826"
),
cardholder_info=ThreeDSCardholderInfo(
name="John Doe",
email="john@me.com"
)
)
authentication = three_ds_client.three_ds_authenticate_session(sessionId, authenticate_three_ds_session_request=authentication_request)
return jsonify(authentication.to_dict()), 200
@app.route('/<sessionId>/challenge-result', methods=['GET'])
def get_challenge_result(sessionId):
config = basistheory.Configuration(
host="https://api.basistheory.com",
api_key="<PRIVATE_API_KEY>"
)
with basistheory.ApiClient(configuration=config) as api_client:
three_ds_client = three_ds_api.ThreeDSApi(api_client)
result = three_ds_client.three_ds_get_challenge_result(session_id=sessionId)
return jsonify(result.to_dict()), 200
if __name__ == '__main__':
app.run(port=4242, debug=True)
In this example, we are using Basis Theory SDK, Go HTTP package and the Gorilla Mux Router.
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"github.com/Basis-Theory/basistheory-go/v5"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/{sessionId}/authenticate", authenticateSession).Methods("POST")
router.HandleFunc("/{sessionId}/challenge-result", getChallengeResult).Methods("GET")
addr := "localhost:4242"
log.Printf("Listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, router))
}
func authenticateSession(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionId := vars["sessionId"]
configuration := basistheory.NewConfiguration()
apiClient := basistheory.NewAPIClient(configuration)
contextWithAPIKey := context.WithValue(context.Background(), basistheory.ContextAPIKeys, map[string]basistheory.APIKey{
"ApiKey": {Key: "<PRIVATE_API_KEY>"},
})
authenticateThreeDSSessionRequest := *basistheory.NewAuthenticateThreeDSSessionRequest()
authenticateThreeDSSessionRequest.SetAuthenticationCategory("payment")
authenticateThreeDSSessionRequest.SetAuthenticationType("payment-transaction")
purchaseInfo := *basistheory.NewThreeDSPurchaseInfo()
purchaseInfo.SetAmount("80000")
purchaseInfo.SetCurrency("826")
purchaseInfo.SetExponent("2")
purchaseInfo.SetDate("20240109141010")
requestorInfo := *basistheory.NewThreeDSRequestorInfo()
requestorInfo.SetId("example-3ds-merchant")
requestorInfo.SetName("Example 3DS Merchant")
requestorInfo.SetUrl("https://www.example.com/example-merchant")
merchantInfo := *basistheory.NewThreeDSMerchantInfo()
merchantInfo.SetMid("9876543210001")
merchantInfo.SetAcquirerBin("000000999")
merchantInfo.SetName("Example 3DS Merchant")
merchantInfo.SetCategoryCode("7922")
merchantInfo.SetCountryCode("826")
cardholderInfo := *basistheory.NewThreeDSCardholderInfo()
cardholderInfo.SetName("John Doe")
cardholderInfo.SetEmail("john@me.com")
authenticateThreeDSSessionRequest.SetPurchaseInfo(purchaseInfo)
authenticateThreeDSSessionRequest.SetRequestorInfo(requestorInfo)
authenticateThreeDSSessionRequest.SetMerchantInfo(merchantInfo)
authenticateThreeDSSessionRequest.SetCardholderInfo(cardholderInfo)
authenticateResponse, _, authenticateErr := apiClient.ThreeDSApi.ThreeDSAuthenticateSession(contextWithAPIKey, sessionId).AuthenticateThreeDSSessionRequest(authenticateThreeDSSessionRequest).Execute()
if authenticateErr != nil {
rw.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(rw).Encode(map[string]string{"error": authenticateErr.Error()})
return
}
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(authenticateResponse)
}
func getChallengeResult(rw http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionId := vars["sessionId"]
configuration := basistheory.NewConfiguration()
apiClient := basistheory.NewAPIClient(configuration)
contextWithAPIKey := context.WithValue(context.Background(), basistheory.ContextAPIKeys, map[string]basistheory.APIKey{
"ApiKey": {Key: "<PRIVATE_API_KEY>"},
})
challengeResult, _, err := apiClient.ThreeDSApi.ThreeDSGetChallengeResult(contextWithAPIKey, sessionId).Execute()
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(rw).Encode(map[string]string{"error": err.Error()})
return
}
rw.WriteHeader(http.StatusOK)
json.NewEncoder(rw).Encode(challengeResult)
}
And then passing that information along back to your frontend.
import { BasisTheory3ds } from "@basis-theory/3ds-web";
const checkout = async () => {
// initializing the 3ds sdk
const bt3ds = BasisTheory3ds("<PUBLIC_API_KEY>");
// creating the session
const session = await bt3ds.createSession({ pan: "<CARD_TOKEN_ID>" });
// authenticating the session
const authResponse = await fetch(
`{your-backend-host}/${session.id}/authenticate`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);
const authentication = await authResponse.json();
if (authentication.status === "challenge") {
// some information about the authentication is necessary to start a challenge
const challengeInfo = {
acsChallengeUrl: authentication.acs_challenge_url,
acsTransactionId: authentication.acs_transaction_id,
sessionId: session.id,
threeDSVersion: authentication.threeds_version,
};
// starting a challenge
// the response is just an object w/ the session id: { id: sessionId }
const challengeCompleted = await bt3ds.startChallenge(challengeInfo);
// getting challenge result
const resultResponse = await fetch(
`{your-backend-host}/${session.id}/challenge-result`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);
const result = await resultResponse.json();
}
};
That's it 🎉!
The result from the authentication
(in case of frictionless) or challenge-result
calls contains the authentication token (authentication_value
attribute) and any other information needed to fully process the 3DS transaction.