Dodgeball Client Trust SDK for JavaScript
Table of Contents
Purpose
Dodgeball enables developers to decouple security logic from their application code. This has several benefits including:
- The ability to toggle and compare security services like fraud engines, MFA, KYC, and bot prevention.
- Faster responses to new attacks. When threats evolve and new vulnerabilities are identified, your application's security logic can be updated without changing a single line of code.
- The ability to put in placeholders for future security improvements while focussing on product development.
- A way to visualize all application security logic in one place.
The Dodgeball Client Trust SDK for JavaScript makes integration with the Dodgeball API easy and is maintained by the Dodgeball team.
Prerequisites
You will need to obtain an API key for your application from the Dodgeball developer center.
Related
Check out the Dodgeball Trust Server SDK for how to integrate Dodgeball into your application's backend.
Installation
Use npm
to install the Dodgeball module:
npm install @dodgeball/trust-sdk-client
Alternatively, using yarn
:
yarn add @dodgeball/trust-sdk-client
Usage
React Applications
The Dodgeball Client SDK comes with a useDodgeball
hook that can be used in all of your components.
You'll first need to initialize the SDK with your public API key which can be found on the Developer Center > API Keys page. This only needs to be done once when the application first loads as in the example below:
import { useDodgeball } from "@dodgeball/trust-sdk-client";
import { useEffect, useSelector } from "react";
import { selectCurrentUser, selectCurrentSession } from "./selectors";
export default function MyApp() {
const dodgeball = useDodgeball("public-api-key...");
const currentSession = useSelector(selectCurrentSession);
const currentUser = useSelector(selectCurrentUser);
useEffect(() => {
/*
When you know the ID of the currently logged-in user,
pass it along with a session ID to dodgeball.track():
*/
dodgeball.track(currentSession?.id, currentUser?.id);
}, [currentSession?.id, currentUser?.id]);
return (
<div>
<h1>My App</h1>
<MyComponent />
</div>
);
}
Below is a simple example of a component that performs a verification when an order is placed:
import { useDodgeball } from "@dodgeball/trust-sdk-client";
import { useState, useEffect } from "react";
import axios from "axios";
export default function MyComponent() {
const dodgeball = useDodgeball(); // Once initialized, you can omit the public API key
const [isPlacingOrder, setIsPlacingOrder] = useState(false);
const [isOrderPlaced, setIsOrderPlaced] = useState(false);
const [isOrderDenied, setIsOrderDenied] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setIsPlacingOrder(false);
}, [isOrderPlaced, isOrderDenied]);
const placeOrder = async (order, previousVerification = null) => {
const sourceToken = await dodgeball.getSourceToken();
const endpointResponse = await axios.post(
"/api/orders",
{ order },
{
headers: {
"x-dodgeball-source-token": sourceToken, // Pass the source token to your API
"x-dodgeball-verification-id": previousVerificationId, // If a previous verification was performed, pass it along to your API
},
}
);
dodgeball.handleVerification(endpointResponse.data.verification, {
onVerified: async (verification) => {
// If an additional check was performed and the request is approved, simply pass the verification ID in to your API
await placeOrder(order, verification.id);
},
onApproved: async () => {
// If no additional check was required, update the view to show that the order was placed
setIsOrderPlaced(true);
},
onDenied: async (verification) => {
// If the action was denied, update the view to show the rejection
setIsOrderDenied(true);
},
onError: async (error) => {
// If there was an error performing the verification, display it
setError(error);
setIsPlacingOrder(false);
},
});
};
const onPlaceOrderClick = async () => {
setIsPlacingOrder(true);
const order = {}; // Fill in with whatever data your API expects
await placeOrder(order);
};
return (
<div>
<h2>My Component</h2>
<p>This component is using the Dodgeball Client SDK.</p>
{isOrderPlaced ? (
<p>Your order was placed!</p>
) : (
<p>
{isOrderDenied && <span>Order was denied. Contact support.</span>}
<button onClick={onPlaceOrderClick} disabled={isPlacingOrder || isOrderDenied}>
{isPlacingOrder ? "Placing Order..." : "Place Order"}
</button>
{error && <div>{error}</div>}
</p>
)}
</div>
);
}
Non-React Applications
The Dodgeball Client SDK exports a Dodgeball
class that can be passed a public API key and an optional config object. See the configuration section for more information on configuration.
You'll first need to initialize the SDK with your public API key which can be found on the Developer Center > API Keys page. This only needs to be done once when the SDK first loads as in the example below:
import { Dodgeball } from "@dodgeball/trust-sdk-client";
const dodgeball = new Dodgeball("public-api-key..."); // Do this once when your application first loads
const sourceToken = await dodgeball.getSourceToken();
When you know the ID of the currently logged-in user, call dodgeball.track()
.
// As soon as you have a session ID, pass it to dodgeball.track()
const onSession = (currentSession) => {
dodgeball.track(currentSession?.id);
};
// When you know the ID of the currently logged-in user, pass it along with a session ID to dodgeball.track()
const onLogin = (currentSession, currentUser) => {
dodgeball.track(currentSession?.id, currentUser?.id);
};
Later, when you want to verify that a visitor is allowed to perform an action, call dodgeball.getSourceToken()
to get a token representing this device. Pass the returned sourceToken
to your API. Once your API returns a response, pass the verification
to dodgeball.handleVerification
along with a few callback functions:
const placeOrder = async (order, previousVerificationId = null) => {
const sourceToken = await dodgeball.getSourceToken();
const endpointResponse = await axios.post(
"/api/orders",
{ order },
{
headers: {
"x-dodgeball-source-token": sourceToken, // Pass the source token to your API
"x-dodgeball-verification-id": previousVerificationId, // If a previous verification was performed, pass it along to your API
},
}
);
dodgeball.handleVerification(endpointResponse.data.verification, {
onVerified: async (verification) => {
// If an additional check was performed and the request is approved, simply pass the verification ID in to your API
await placeOrder(order, verification.id);
},
onApproved: async () => {
// If no additional check was required, update the view to show that the order was placed
setIsOrderPlaced(true);
},
onDenied: async (verification) => {
// If the action was denied, update the view to show the rejection
setIsOrderDenied(true);
},
onError: async (error) => {
// If there was an error performing the verification, display it
setError(error);
setIsPlacingOrder(false);
},
});
};
API
Configuration
The package requires a public API key as the first argument to the constructor.
const dodgeball = new Dodgeball("public-api-key...");
Optionally, you can pass in several configuration options to the constructor:
const dodgeball = new Dodgeball("public-api-key...", {
// Optional configuration
apiVersion: "v1",
apiUrl: "https://api.dodgeballhq.com",
logLevel: "ERROR",
disableCookies: true,
});
Option | Default | Description |
---|---|---|
apiVersion | v1 | The Dodgeball API version to use. |
apiUrl | https://api.dodgeballhq.com | The base URL of the Dodgeball API. Useful for sending requests to different environments such as https://api.sandbox.dodgeballhq.com . |
logLevel | INFO | The level of logging to use. Possible options are TRACE , INFO , ERROR , or NONE . TRACE and INFO are useful for debugging. ERROR only logs errors. NONE turns off all logging. |
disableCookies | false | Whether to disable cookies. Setting this to true will decrease source accuracy but is useful for applications that cannot set cookies. |
Identify the User
When you know the ID of the currently logged-in user, call dodgeball.track()
.
dodgeball.track("session_def456...", "user_abc123...");
Option | Default | Description | Required |
---|---|---|---|
sessionId | null | The ID of the current session. Submit this as soon as you have a session ID. | true |
userId | null | The ID of the currently logged-in user. Submit as soon as you have an authenticated user. | false |
Get a Source Token
A sourceToken
is compiled from all of the enabled tracking and identifying integrations and represents a the current device. It is used to represent the device when performing a verification.
Important: Source tokens are short-lived and should be requested immediately before calling your API. They should not be stored or cached for future use. Requests made to Dodgeball with expired source tokens will be rejected.
const sourceToken = await dodgeball.getSourceToken();
Call your API
Every application has key moments of risk. At these key moments, your application needs to check that the request should be allowed to proceed. A few examples include: registration, login, placing an order, creating a listing, leaving a review, adding a payment method, changing bank account information, redeeming a coupon, and transferring funds.
In Dodgeball, these key moments of risk are called checkpoints. Your application's API is responsible for calling these checkpoints and returning their response to your application's frontend. Dodgeball maintains server-side SDKs to easily handle calling a checkpoint and parse its response.
const endpointResponse = await axios.post(
"/api/orders",
{ order },
{
headers: {
"x-dodgeball-source-token": sourceToken, // Pass the source token returned from 'dodgeball.getSourceToken()'
"x-dodgeball-verification-id": previousVerificationId, // If a previous verification was performed, pass it along to your API
},
}
);
- Note: There's no requirement to use
x-dodgeball-source-token
orx-dodgeball-verification-id
headers to submit information to your API. You may use different headers, send them in the body, or use whatever other method makes the most sense for your application.
Handle a Verification
When a checkpoint is called a verification is created. A verification represents the checks that have been performed on a request and whether or not it should be allowed to proceed. Sometimes it is necessary to gather additional information from the user before making a determination about whether or not the request is allowed to proceed. For example: requiring a 2FA check before allowing a user to login, but only if this is a new device or its IP address is far from the last IP address used by the user. In this case, your application's API should return the verification to your application's frontend without performing the requested operation. Your application's frontend should then call dodgeball.handleVerification()
with the verification and a few callback functions:
dodgeball.handleVerification(endpointResponse.data.verification, {
onVerified: async (verification) => {
// If an additional check was performed and the request is approved, simply pass the verification ID in to your API
await placeOrder(order, verification.id);
},
onApproved: async () => {
// If no additional check was required, update the view to show that the order was placed
},
onDenied: async (verification) => {
// If the action was denied, update the view to show the rejection
},
onError: async (error) => {
// If there was an error performing the verification, display it
},
});
Available Callbacks
Callback | Required | Description |
---|---|---|
onVerified(verification) | true | Called when an additional check was performed and the request to your application's API should be retried. Be sure to pass the ID of the verification to your application's API. See call your API for more details. |
onApproved(verification) | true | Called when the verification is approved and the request succeeded. |
onDenied(verification) | false | Called when the verification is denied. |
onPending(verification) | false | Called when the verification is pending. |
onBlocked(verification) | false | Called when the verification is blocked and requires more input. |
onUndecided(verification) | false | Called when the verification is complete but undecided. |
onError | false | Called if there was an error performing the verification . |
Verification
Property | Description |
---|---|
id | The verification ID. |
status | The current status of the verification . See verification statuses for possible statuses. |
outcome | The current outcome of the verification. See the verification outcomes for possible outcomes. |
Verification Statuses
Status | Description |
---|---|
COMPLETE | The verification was completed successfully. |
PENDING | The verification is currently processing. |
BLOCKED | The verification is waiting for input from the user. |
FAILED | The verification encountered an error and was unable to proceed. |
Verification Outcomes
Outcome | Description |
---|---|
APPROVED | The request should be allowed to proceed. |
DENIED | The request should be denied. |
PENDING | A determination on how to proceed has not yet been reached. |
ERROR | The verification encountered an error and was unable to make a determination on how to proceed. |
Possible Verification States
The states listed here are for reference. You will typically not need to interpret a verification directly. Instead use the callbacks described in available callbacks.
Approved
const verification = {
id: "verification-id...",
status: "COMPLETE",
outcome: "APPROVED",
};
When a request is allowed to proceed, the verification status
will be COMPLETE
and outcome
will be APPROVED
.
Denied
const verification = {
id: "verification-id...",
status: "COMPLETE",
outcome: "DENIED",
};
When a request is denied, verification status
will be COMPLETE
and outcome
will be DENIED
.
Pending
const verification = {
id: "verification-id...",
status: "PENDING",
outcome: "PENDING",
};
If the verification is still processing, the status
will be PENDING
and outcome
will be PENDING
.
Blocked
const verification = {
id: "verification-id...",
status: "BLOCKED",
outcome: "PENDING",
};
A blocked verification requires additional input from the user before proceeding. When a request is blocked, verification status
will be BLOCKED
and the outcome
will be PENDING
.
Undecided
const verification = {
id: "verification-id...",
status: "COMPLETE",
outcome: "PENDING",
};
If the verification has finished, with no determination made on how to proceed, the verification status
will be COMPLETE
and the outcome
will be PENDING
.
Error
const verification = {
id: "verification-id...",
status: "FAILED",
outcome: "ERROR",
error: "...",
};
If a verification encounters an error while processing (such as when a 3rd-party service is unavailable), the status
will be FAILED
and the outcome
will be ERROR
. The error
property will contain a message describing the error that occurred.
Utility Methods
There are several utility methods available to help interpret a verification
. It is strongly advised to use them rather than directly interpreting a verification
. You will typically not need to use these methods directly. Instead use the callbacks described in available callbacks.
dodgeball.isAllowed(verification)
The isAllowed
method takes in a verification and returns true
if the request is allowed to proceed.
dodgeball.isDenied(verification)
The isDenied
method takes in a verification and returns true
if the request is denied and should not be allowed to proceed.
dodgeball.isRunning(verification)
The isRunning
method takes in a verification and returns true
if an additional input must be gathered from the user. See the verification handling section for more details on use and an end-to-end example.
dodgeball.isUndecided(verification)
The isUndecided
method takes in a verification and returns true
if the verification has finished and no determination has been reached on how to proceed.
dodgeball.hasError(verification)
The hasError
method takes in a verification and returns true
if it contains an error.
End-to-end Examples
Approved without any additional input
For most traffic, the request will be approved without requiring any additional input from the user. For example, when placing an order the sequence would be:
- The
Frontend
makes a POST request to theBackend
to place an order. - The
Backend
calls a PLACE_ORDER checkpoint inDodgeball
. Dodgeball
creates averification
and determines that the request should be allowed to proceed.- The
Backend
creates the order. - The
Backend
returns the order to theFrontend
. There is no need to include theverification
in the response. - The response is passed to
dodgeball.handleVerification(...)
, which calls theonApproved
callback. - The view is updated to indicate that the order was placed successfully.
Denied without any additional input
For some traffic deemed too risky, the request will be denied without requiring any additional input from the user. For example, when placing an order the sequence would be:
- The
Frontend
makes a POST request to theBackend
to place an order. - The
Backend
calls a PLACE_ORDER checkpoint inDodgeball
. Dodgeball
creates averification
and determines that the request should be denied.- The
Backend
returns theverification
to theFrontend
. - The response is passed to
dodgeball.handleVerification(...)
, which calls theonDenied
callback. - The view is updated to indicate that the order was denied.
Additional input required
For some traffic, additional input may be required from the user. For example, when placing an order the sequence would be:
- The
Frontend
makes a POST request to theBackend
to place an order. - The
Backend
calls a PLACE_ORDER checkpoint inDodgeball
. Dodgeball
creates averification
and determines that additional input is required from the user.- The
Backend
returns theverification
to theFrontend
. - The response is passed to
dodgeball.handleVerification(...)
, which handles gathering additional input from the user. In this example, a 2FA code is requested from the user. - The user completes any additional checks required by the checkpoint.
- Once all additional checks are completed, the verification is approved.
- The
onVerified
callback passed tododgeball.handleVerification(...)
, is called. This indicates that the request should be retried. - The
Frontend
makes a POST request to theBackend
to place an order, this time including the ID of theverification
that was approved. - The
Backend
calls a PLACE_ORDER checkpoint inDodgeball
, this time including the ID of theverification
that was approved in theuseVerificationId
parameter. - To prevent replay attacks, the passed
verification
is marked as used. Dodgeball
returns theverification
to theBackend
.- The
Backend
creates the order. - The
Backend
returns the order to theFrontend
. There is no need to include theverification
in the response. - The response is passed to
dodgeball.handleVerification(...)
, which calls theonApproved
callback. - The view is updated to indicate that the order was placed successfully.