If you have a single API which accepts requests from both the web and mobile channels, then you may appreciate the ability to have shared code for granting access to protected data. The Approov flow has been extended with a facility for integrating other web protection services to enable this.
Approov wraps web protection service results and the Approov service performs the web protection service call normally performed by your backend API. In the Approov configuration you define the duration of an Approov session which is the time period during which a web protection service result, once obtained, should be considered to be valid. During an Approov session no new web protection service results need to be requested, but instead many (short-lived) Approov tokens that contain the original web protection service result can be obtained from the Approov service. This is underpinned by a refresh token, valid for the duration of the Approov session, that is used by the Approov web SDK internally. The Approov service ensures that valid Approov tokens can only be obtained from the browser session that made the initial Approov token request that included the web protection result by using the Demonstrating Proof of Possession (DPoP) protocol. The Approov web SDK can generate a DPoP which that you can, optionally, include in requests to your API in order to provide confidence that the browser session that requested the Approov token is the one that is sending the web API request and that the request has not been intercepted and modified.
The server-side flows of several, best in class, browser solutions have been integrated into Approov to communicate their results back to browsers wrapped in an untamperable Approov token. At a minimum, this enables a single simple token check in the backend API to authorize access. However, if required, Approov can be configured to embed the full results of the integrated protection to allow for deeper interrogation.
There is support for the following web protection services:
To use an integration, you must sign up for the associated service but then follow modified instructions for using those services described in the associated section under Implementing the Integrated Services Flow.
Just because you can doesn’t mean you should. The primary Approov service can provide a very strong indication that a request originates from an untampered instance of your app running on a user device. The integrated web services are not attempting to solve this issue directly and therefore cannot provide the same level of confidence, even if combining multiple integrations. (Even without a specific attack, the built in debug facilities of browsers make it too easy for users to copy tokens, or other data, that a service provider would prefer to be kept private.) As such, we recommend that you restrict critical API endpoints to only work from the Mobile App. The solution presented here, supports this: after successfully verifying a token signature, you can always look at the token claims to determine if it was issued for the web or mobile channel and use that to inform your access control logic.
In some cases, it may be necessary to have a more involved access check. For example, the mobile app token includes a did (DeviceID) claim that uniquely identifies the particular install of an app. If required, the protected API can associate this ID with a user’s account. Such an association can then be checked on each API request to identify those requests that don’t match and then highlight them as having a heightened risk of fraud. The mismatch can be used to trigger additional security steps that determine if the difference is caused by an attempt to misuse the credentials from another valid app instance, or because the legitimate account owner is logging in from a different mobile device.
In the web-flow we can achieve the same kind of check by embedding the results of a Fingerprint server API query in the Approov token. This exposes the Fingerprint visitorID to the backend API, enabling the same kind of association as the DeviceID and a corresponding security flow when a new visitorID is identified for a user. A similar binding is also possible using the user identifiers available to enterprise level hCaptcha customers. In a similar vein, the Google reCAPTCHA or hCaptcha services can be used to provide confidence that requests have not been scripted.
The combined Mobile and Web flows are shown in the following diagram:
The left side of the diagram illustrates the primary Approov mobile app protection flow:
The right side of the diagram shows the parallel web protection flow, with one or more integrations in place and using a common backend API:
The following steps are required to set up a web protection service.
The first two steps, dealing with server side changes, are covered in the next section. Steps 3 through 7 are specific to the integrated service and the sections, Fingerprint, Google reCAPTCHA, and hCaptcha take you through those steps for each of the associated services, followed by a section describing how to generate and verify DPoP tokens. The section on Web Protection Metrics Presentation covers step 8 and completes the basic flow. A final section on web protection provides a reference for the web SDK and also covers more advanced topics: using multiple integrated services, obtaining tokens for multiple API calls in one request, adding token binding to your web app, and troubleshooting.
The first step is to audit the target API(s) to decide which endpoints or queries will service both web and mobile requests and those that will be unique to one or the other. As mentioned previously, web protections do not provide the same level of confidence as the Approov mobile API protection service, that a request has not been constructed by a threat actor. The table below shows the claims that can be used to distinguish between a token issued to a mobile app and one issued to a web app:
Claim | Description |
---|---|
did |
The did claim is present in all tokens obtained from the mobile app attestation channel. It identifies the unique ID for attesting device. Testing for it's presence is the easiest way to identify tokens originating from this channel whether they come from the Primary service or from the Failover. |
embed |
The embed claim is present in all tokens obtained from the web protection channel. It holds a JSON object whose keys indicate the specific web protection service, or services, that were used to obtain the token and whose values are brief summaries, or the full results, obtained from the wrapped services. If it is important to query the results obtained from the 3rd party service in your backend system, then you can also use the presence of the claim itself to determine which channel created the token. Note that, because the full web protection results may contain entries that must be kept private in order to maintain solution security, you must use encrypted tokens (JWEs) to include it in the generated Approov token. |
Note that, in either case you should first ensure that the token you have received is valid, signed in the expected way with the correct secret. See the section on Backend integrations.
If you have an existing mobile only API that you are considering opening up to the web channel, then it is imperative that you first perform some kind of end-point audit to identify those API entries that could be enabled for web-access, those that shouldn’t, and those that mustn’t. As mentioned previously, Approov mobile API protection provides a strong degree of confidence that requests originate from legitimate installs of your apps. This confidence cannot be reproduced for the web channel.
Use the Approov CLI to enable the web-flow for your APIs. The section on adding APIs with the Approov CLI covers all the options available when adding an API. Web protection is included in that documentation, however, in short, Approov web protection is enabled for an API when it is added with the -allowWeb
flag. If you wish to enable web protection for an existing API definition you just add it again using the same parameters as before but also including the -allowWeb
flag. To disable web protection, you add the API again but without the flag. Each addition of the same API, completely overwrites the previous settings for that API.
approov api -add your.domain -allowWeb
This will output something like the following and requires confirmation:
WARNING: adding the API will have an immediate impact on your apps in production.
ATTENTION: If you wish to continue then please type YES and return: YES
added API domain your.domain with type:account, alg:HS256, pin:JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=
enabled continuous monitoring for https://your.domain:443 (remove using `approov monitoring -removeAPI`)
To receive a token at all, all Approov token fetches must include at least the web-enabled target API. So, if you are adding your first integrated service it may be useful to first add a web-enabled development API domain that you can include in your test requests.
Note that, if you intend to embed the full results from the integrated protection server-side lookup, then you must also use encrypted tokens for the API to prevent leaking potentially sensitive data from the integrated protection. Only APIs configured to use encrypted tokens will get the full results embedded in the token. An API will use an encrypted token if it is configured to use a JWK with an encryption algorithm, or, if you use the -jwe
flag, then the default encryption secret for your account will be used to encrypt tokens with the A256GCMKW algorithm:
approov api -add your.domain -allowWeb -jwe
This will output something like the following and requires confirmation:
WARNING: adding the API will have an immediate impact on your apps in production.
ATTENTION: If you wish to continue then please type YES and return: YES
added API domain your.domain with type:account, alg:HS256, pin:JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=
enabled continuous monitoring for https://your.domain:443 (remove using `approov monitoring -removeAPI`)
You must change your back-end server to accept the new token type before changing the token signing algorithm for your active APIs, including the addition of the -jwe flag. Ideally, the server should be altered to accept both the new and old token type while the new token format propagates through the flow.
Several sections provide details on handling APIs, secrets, and encryption: Adding API Domains, Managing Keysets, JWE Token Encryption, and Account Secret Key Export.
Fingerprint provides a powerful mechanism for fingerprinting the browser environment of a user and deriving a visitor ID based on both the raw fingerprint and the history of prior visits for that user. This visitor ID can then be compared against a user identity in the backend system to determine if they match. If they do, then there is a high probability that it is indeed the correct user and operations can proceed. If not, then this may indicate an attempt at account takeover or simply that the user has moved to a different browser environment. In either case, additional verification steps should be introduced into the flow to protect the user’s account.
Before proceeding with an integration you must sign up for an account: Fingerprint Signup. Each Fingerprint account can have one or more subscriptions and each subscription needs a Fingerprint public API key and secret API key to work. You should follow the most appropriate JavaScript agent documentation to the point where you obtain an fp.get()
result.
Importantly, for best results from the Fingerprint service, you should follow the instructions for adding a subdomain to handle Fingerprint queries: Subdomain setup.
A Fingerprint subscription can be added by specifying the subscription region and by providing a valid Fingerprint public API key and secret API key. You can add multiple subscriptions: the public API key identifies the target subscription for Approov web protection. The following command would add a subscription in the Rest-of-the-World (RoW) region leaving all other settings at their default values:
approov web -fingerprint -add your-Fingerprint-public-API-key -secret your-Fingerprint-secret-API-key -region RoW
This will output something like the following and requires confirmation:
WARNING: changing the web configuration will have an immediate impact in production
ATTENTION: If you wish to continue then please type YES and return: YES
fingerprint subscription your-Fingerprint-public-API-key added
Listing all the configured web integration properties shows that the new subscription has been added:
approov web -list
Will output something like the following:
Site Key: your-Approov-site-key
Token Lifetime: 120 seconds
Refresh Token Lifetime: 7200 seconds
Fingerprint:
Optional: true
Subscription Key: your-Fingerprint-public-API-key
Region: RoW
Max Elapsed Time: 2.00s
Max Bot Probability: 1.00
Embed Result: false
Max Result Lifetime: 0s
The following table lists properties in order and provides their description:
Property Name | Description | |
Site Key | This is the Approov site key you need to use to identify your Approov account in web protection API requests | |
Token Lifetime | This is the lifetime given to Approov tokens obtained from the Approov web protection service. It may be changed using approov web -setTokenLifetime <seconds> . |
|
Refresh Token Lifetime | This specified the lifetime of the refresh token and determines the duration of an Approov session. The length of an Approov session is also limited by the maximum lifetime of any result obtained from Fingerprint's server (see Max Result Lifetime below). When the Approov session ends, any result obtained from Fingerprint's server must be renewed. The refresh token lifetime may be changed using approov web -setRefreshTokenLifetime <seconds> . |
|
Fingerprint | The heading that separates Fingerprint specific properties from general properties and those of other integrations. | |
Optional | Indicates whether it is optional to provide the result of a Fingerprint identification request for the initial token fetch of an Approov session or not. This defaults to true which allows a valid Approov token to be provided for web protection using another configured integration even if a Fingerprint result is not provided. This property may be unset using approov web -fingerprint -setRequired and reset using approov web -fingerprint -setOptional . |
|
Subscription Key | Displays the <Fingerprint-public-API-key> used to configure the subscription and serves as the heading for properties related to just this subscription. Note that the <Fingerprint-secret-API-key> is not listed; it is never displayed or logged by Approov. If you use the Fingerprint dashboard to change the secret API key for a subscription, then you can always update the subscription by adding it again with the new Fingerprint secret API key. |
|
Region | The configured region for the subscription, either RoW or EU . This must match the region specified for the subscription in the Fingerprint dashboard. |
|
Max Elapsed Time | Specifies the maximum permitted time between a Fingerprint get call and the subsequent Approov token fetch request. The 2 second default matches the delay enforced by the Google reCAPTCHA and hCaptcha flows. To change this value, use the -maxElapsedTime <duration> parameter when you add the subscription. |
|
Max Bot Probability | One of the values returned from a Fingerprint server-side lookup is a BotProbability score. Approov will return invalid tokens if the Fingerprint lookup returns a score higher than the configured max. The default, 1.0, will accept all scores. To change this value, use the -maxBotProbability <score> parameter when you add the subscription. |
|
Embed Result | If this is true *and you have chosen to use encrypted tokens*, JWEs, for the target API then the full Fingerprint result object will be included in generated Approov tokens. It defaults to false , use -embedResult when you add a subscription to turn it on. |
|
Max Result Lifetime | This specifies the maximum lifetime of any result obtained from Fingerprint's server, after which it must be renewed. It can be set by using the -maxResultLifetime <value> parameter when you add the subscription. A value of 0, the default, denotes that it is equal to the refresh token lifetime. The duration of an Approov session is determined by the smaller of the maximum result lifetime and the refresh token lifetime (see Refresh Token Lifetime above). | |
Rate Limit | This is not in the above list because no rate limit was specified, but it may be configured when you add a subscription to specify the maximum number of requests per minute: -rateLimit <limit> . Attempts to make requests beyond the limit will result in an error. The limit is specified in terms of the maximum number of requests per minute. If this is not specified, or set to 0, then no limit is imposed. |
To change a subscription you simply add it again with all the properties required for the changed subscription. Each addition of the same Fingerprint public API key completely overwrites the previously stored entry.
Once the site is set up in Approov (and you’ve waited a couple minutes or so for the configuration to propagate), you can try retrieving an Approov token using the Approov web SDK. For the full documentation of the Approov web SDK please refer to the reference section. The web SDK can be downloaded using the Approov command line tool:
approov sdk -packageID approov.js.zip -getClientPackage approov.js.zip
This writes the latest available web SDK package to the approov.js.zip
file. Unzip the file, copy approov.js
into your project and load it as part of your web app.
Some example invocations of the Approov web SDK functions are provided below, where the api
argument is your Approov protected API domain and result
is the result of a Fingerprint identification request made by calling Fingerprint’s get()
function. Once an Approov session has been initialized, the first Approov token fetch must include the result of a recent Fingerprint identification request, but this is not required for any subsequent Approov token fetches during the same Approov session:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize the Approov session
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
fingerprintPublicAPIKey: 'your-Fingerprint-public-API-key'
})
// ...
// Initial token fetch of Approov session provides the result of the Fingerprint
// identification request
const approovTokenPromise = Approov.fetchToken(api, {fingerprintIDResult: result})
// Subsequent token fetches during an Approov session don't require the result of
// the Fingerprint identification request
const approovTokenPromise = Approov.fetchToken(api, {})
With a small modification the example code provided by Fingerprint can be adapted to call the Approov web SDK function. The example code below is based on the Fingerprint Quick Start Guide and shows how to integrate Fingerprint into a web page so that a Fingerprint identification result is included with the API request:
<html>
<head>
<title>Fingerprint Demo</title>
<script>
// Initialize the Fingerprint agent at application startup.
const fpPromise = import('https://fpjscdn.net/v3/' +
'your-Fingerprint-public-API-key')
.then(FingerprintJS => FingerprintJS.load())
</script>
</head>
<body>
<script>
function onClick(event) {
// Get the Fingerprint visitor identifier when you need it.
fpPromise
.then(fp => fp.get())
.then(fingerprintResult => {
// Perform fetch call to API, passing Fingerprint identification result
// in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Fingerprint-Result': fingerprintResult
},
body: JSON.stringify(/* JSON data to send */)
}).then(response => {
// Handle API response
})
})
}
</script>
<button id="submit">Submit</button>
<script>
document.getElementById('submit').onclick = onClick
</script>
</body>
</html>
There are few changes needed for integrating Approov into this flow. Add a function (fetchApproovToken
) to obtain an Approov token and then call this in place of the Fingerprint identification request call and include the obtained Approov token in the API request. The function fetchApproovToken
shows how to integrate Approov web protection in a single function using lazy (re-)initialization of Approov sessions:
<html>
<head>
<title>Fingerprint Demo</title>
<script>
// Initialize the Fingerprint agent
const fpPromise = import('https://fpjscdn.net/v3/' +
'your-Fingerprint-public-API-key')
.then(FingerprintJS => FingerprintJS.load())
</script>
</head>
<body>
<script>
// Fetches an Approov token and refreshes a Fingerprint identification
// result if required
async function fetchApproovToken(api) {
let {Approov, ApproovSessionError} = await import("./approov.js")
try {
// Try to fetch an Approov token
let approovToken = await Approov.fetchToken(api, {})
return approovToken
} catch (error) {
if (error instanceof ApproovSessionError) {
// If Approov has not been initialized yet or the Approov session has
// expired, initialize and start a new session
await Approov.initializeSession({
approovHost: "web-1.approovr.io",
approovSiteKey: "your-Approov-site-key",
fingerprintPublicAPIKey: "your-Fingerprint-public-API-key"
})
// Get a fresh Fingerprint result
let result = await (fpPromise.then(fp => fp.get()))
// Fetch the Approov token
let approovToken = await Approov.fetchToken(api,
{fingerprintIDResult: result})
return approovToken
} else {
throw error
}
}
}
function onClick(event) {
// Get an Approov token
fetchApproovToken("your-API-domain").then(approovToken => {
// Perform fetch call to API, passing Approov token in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Approov-Token': approovToken
},
body: JSON.stringify(/* JSON data to send */)
}).then(response => {
// Handle API response
})
})
}
</script>
<button id="submit">Submit</button>
<script>
document.getElementById('submit').onclick = onClick
</script>
</body>
</html>
embed
token claim for FingerprintYou may wish to record or evaluate the Fingerprint results in your backend system. Fingerprint properties are added to the embed
claim of the Approov token which is a JSON object with a property called fp:your-Fingerprint-public-API-key
, where 'your-Fingerprint-public-API-key'
is the Fingerprint public API key registered to your account and used in the Approov token request. By default only the visitorId and requestId are included in the token (using the same JSON structure as the full Fingerprint result). The following example shows the full decoded body of an Approov token with the default properties embedded from a Fingerprint request lookup:
{
"exp": 1620146455,
"ip": "1.2.3.4",
"arc": "LL3BRGIELU",
"embed": {
"fp:your-Fingerprint-public-API-key": {
"visitorId": "eZsCHxhztqEOX0ZmOlwi",
"visits": [
{
"requestId": "lsodLAU4ubccLnMzrI3N"
}
]
}
}
}
If the -embedResult
flag is used when you register the Fingerprint subscription and you have chosen to use encrypted tokens, JWEs, for the target API then the full results are included in the token. A slightly abbreviated example is shown below:
{
"exp": 1620128085,
"ip": "1.2.3.4",
"arc": "EAM2W37PSU",
"embed": {
"fp:your-Fingerprint-public-API-key": {
"visitorId": "eZsCHxhztqEOX0ZmOlwi",
"visits": [
{
"requestId": "Otc6rGJcax7PrUuby7GA",
"browserDetails": {
"browserName": "Chrome",
"browserMajorVersion": "90",
"browserFullVersion": "90.0.4430",
"os": "Linux",
"device": "Other",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36",
"botProbability": 0
},
"incognito": false,
"ip": "1.2.3.4",
"ipLocation": { "<IP-keys...>": "<IP-values...>" },
"time": "2021-05-04T11:32:30Z",
"timestamp": 1620127950391,
"url": "<URL-of-the-calling-webpage>",
"tag": {}
}
]
}
}
}
You should now be set up to generate and receive web protection Approov tokens using Fingerprint. For the full reference on the web protection API and the more advanced features that are provided, please see the reference section after the integration summaries. For how to verify Approov tokens in your backend API server please see the section Backend Integration of the Approov Reference Guide and for information as to how to make use of the DPoP (Demonstrating Proof of Possession) token please refer to DPoP Token Use and Verification.
Google reCAPTCHA is a popular service to determine if a browser is being operated by a human. A browser request retrieves a token from the reCAPTCHA service which is then passed to the protected API as part of a request to be processed. A second request from the backend API obtains the full set of results associated with the token and uses this to determine whether to accept or reject the original request.
Before proceeding with an integration you must sign up for a key-pair; there’s a link from the developer guide introduction: Google reCAPTCHA. The Approov integration supports v2 (tickbox or badge) and v3. Each key-pair has a site key and a secret which you will use to register the site with Approov. Continue to follow the developer guide for your selected reCAPTCHA flow; the instructions below will show how to modify the flow to add an Approov token fetch request.
A site can be added to Approov by providing the reCAPTCHA site key and the associated secret. You can add multiple sites: the site key is the unique key that identifies the target site for Approov web protection. The following command adds a site to your Approov account leaving all other settings at their default values:
approov web -recaptcha -add your-reCAPTCHA-site-key -secret your-reCAPTCHA-secret
This will output something like the following and requires confirmation:
WARNING: changing the web configuration will have an immediate impact in production
ATTENTION: If you wish to continue then please type YES and return: YES
recaptcha site your-reCAPTCHA-site-key added
Listing all the configured web protection properties shows that the new site has been added:
approov web -list
This will output something like the following:
Site Key: your-Approov-site-key
Token Lifetime: 120 seconds
Refresh Token Lifetime: 7200 seconds
reCAPTCHA:
Optional: true
Site Key: your-reCAPTCHA-site-key
Min Score: 0.00
Include IP: false
Embed Result: true
Max Result Lifetime: 0s
The following table lists properties in order and provides a description:
Property Name | Description | |
Site Key | This is the Approov site key you need to use to identify your Approov account in web protection API requests | |
Token Lifetime | This is the lifetime given to Approov tokens obtained from the Approov web protection service. It may be changed using approov web -setTokenLifetime <seconds> . |
|
Refresh Token Lifetime | This specified the lifetime of the refresh token and determines the duration of an Approov session. The length of an Approov session is also limited by the maximum lifetime of any result obtained from reCAPTCHA's server (see Max Result Lifetime below). When the Approov session ends, any result obtained from reCAPTCHA's server must be renewed. The refresh token lifetime may be changed using approov web -setRefreshTokenLifetime <seconds> . |
|
reCAPTCHA | The heading that separates reCAPTCHA specific properties from general properties and those of other integrations. | |
Optional | Indicates whether it is optional to provide a reCAPTCHA result for the initial token fetch of an Approov session or not. This defaults to true which allows a valid Approov token to be provided for web protection using another configured integration even if a reCAPTCHA result is not provided. This property may be unset using approov web -recaptcha -setRequired and reset using approov web -recaptcha -setOptional . |
|
Site Key | Displays the reCAPTCHA site key used to configure the site and serves as the heading for properties related to just this site. Note that the reCAPTCHA secret is not listed; the secret is the private key for the site and it is never displayed or logged by Approov. | |
Domains | This lists the domains registered against the site; the entry is omitted if no domains were specified. Multiple domains can be registered by using the -domain flag multiple times. The backend reCAPTCHA lookup performed by Approov returns the domain that performed the reCAPTCHA. If one or more domains are defined, then the returned domain must be found in this list. If no domains are registered, then a domain check is not performed. This facility can be used to selectively enable some of the domains that you have registered with the reCAPTCHA site in Google. |
|
Actions | This is not in the above list because no actions were specified as the site was added but the entry lists the actions registered against the site using the -action <value> argument. Multiple actions may be registered by using the -action flag multiple times. The backend reCAPTCHA lookup performed by Approov may return the action that performed the reCAPTCHA(v3 only); if the list of actions registered with Approov is non-empty, then the returned action string must be in the list for Approov to return a valid token. If no actions are registered, or if no action is provided by the backend reCAPTCHA lookup, then the check is not performed. |
|
Min Score | The Score returned from a reCAPTCHA server-side lookup represents the probability that the website user is a human. Approov will return invalid tokens if the reCAPTCHA lookup returns a score lower than the configured min. The default, 0, will accept all scores. To change this value, use the -minScore <score> argument when you add a site. |
|
Embed Result | If this is true and you have chosen to use encrypted tokens, JWEs, for the target API then the full reCAPTCHA result object will be included in generated Approov tokens. It defaults to false , use -embedResult when you add a site to turn it on. |
|
Max Result Lifetime | This specifies the maximum lifetime of any result obtained from reCAPTCHA's server, after which it must be renewed. It can be set by using the -maxResultLifetime <value> parameter when you add the subscription. A value of 0, the default, denotes that it is equal to the refresh token lifetime. The duration of an Approov session is determined by the smaller of the maximum result lifetime and the refresh token lifetime (see Refresh Token Lifetime above). | |
Rate Limit | This is not in the above list because no rate limit was specified, but it may be configured when you add a site to specify the maximum number of requests per minute: -rateLimit <limit> . Attempts to make requests beyond the limit will result in an error. The limit is specified in terms of the maximum number of requests per minute. If this is not specified, or set to 0, then no limit is imposed. |
To change a site’s properties you simply add it again with all the properties required for the change. Each addition of the same site key completely overwrites the previously stored entry.
Once the site is set up in Approov (and you’ve waited a couple minutes or so for the configuration to propagate), you can try retrieving an Approov token using the Approov web SDK. For the full documentation of the Approov web SDK please refer to the reference section. The web SDK can be downloaded using the Approov command line tool:
approov sdk -packageID approov.js.zip -getClientPackage approov.js.zip
This writes the latest available web SDK package to the approov.js.zip
file. Unzip the file, copy approov.js
into your project and load it as part of your web app.
Some example invocations of the Approov web SDK functions are provided below, where the api
argument is your Approov protected API domain and token
is the reCAPTCHA token obtained by calling the grecaptcha.getResponse()
function. Once an Approov session has been initialized, the first Approov token fetch must include recent reCAPTCHA token, but this is not required for any subsequent Approov token fetches during the same Approov session:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize the Approov session
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
recaptchaSiteKey: 'your-reCAPTCHA-site-key'
})
// ...
// Initial token fetch of Approov session provides the reCAPTCHA token
const approovTokenPromise = Approov.fetchToken(api, {recaptchaToken: token})
// Subsequent token fetches during an Approov session don't require the reCAPTCHA
// token
const approovTokenPromise = Approov.fetchToken(api, {})
The Approov token should never be logged in production code.
The precise code required to obtain the reCAPTCHA token and then call the above function will depend on the reCAPTCHA version and mechanism that you have selected. In each case, after obtaining the reCAPTCHA token, you next pass it to Approov.fetchToken
before passing the resulting Approov token to your backend API. The discussion below focuses on changes to the Google docs for reCAPTCHA v3, however, the transformations are applicable to any of the reCAPTCHA flows.
The simplest example provided for reCAPTCHA v3 Checkbox style, Automatically bind the challenge to a button, is to introduce a button
with the g-recaptcha
class inside the form in which the reCAPTCHA challenge will be issued. In this approach, the HTML in the docs doesn’t retrieve the reCAPTCHA token that results from the challenge, instead it is embedded in the form data and submitted from there. To use Approov in the same style, you would need to ensure that the Approov token is also added to a hidden form input field before the form is submitted. The downside of this approach is that a user action is required (clicking the submit button) between the event that retrieves the approovToken and the form submission. This means that the Approov token lifetime is likely to be too short. Extending the token lifetime is not recommended unless network latency is causing token expiry before it can be processed by your backend API.
The example code below is based on the reCAPTCHA v3 Documentation and shows how to integrate reCAPTCHA into a web page so that a hidden reCAPTCHA token is included with the form data.
<html>
<head>
<title>reCAPTCHA Demo</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<script>
function onSubmit(token) {
document.getElementById("demo-form").submit();
}
</script>
<form id="demo-form">
<!-- Form data inputs ... -->
<br/>
<button class="g-recaptcha" data-sitekey="your-reCAPTCHA-site-key"
data-callback='onSubmit'
data-action='submit'>Submit</button>
</form>
</body>
</html>
This approach does not permit the form submission to be intercepted for the purpose of including an Approov token with the form data. The solution is to override the form’s default submit behavior so that the Approov token fetch can be inserted in the flow. This approach requires that your javascript code constructs and issues the request to your API, however, this is also best practice because you can then adjust the request to precisely match the one used by your mobile app - which is highly likely to include some properties in request headers, notably the Approov token.
The first step for integrating Approov into this flow is to modify the form’s behavior to call a Javascript function (onClick
) when the submit button is clicked. The onClick
function programmatically invokes a reCAPTCHA challenge and includes the obtained reCAPTCHA token with the API request:
<html>
<head>
<title>reCAPTCHA Demo</title>
<script src="https://www.google.com/recaptcha/api.js?
render=your-reCAPTCHA-site-key">
</script>
</head>
<body>
<script>
function onClick(event) {
event.preventDefault();
// Convert the form data to JSON
// ...
// Perform the challenge and get a reCAPTCHA token
grecaptcha.ready(function() {
grecaptcha.execute('your-reCAPTCHA-site-key', {
action: 'submit'
}).then(function(reCaptchaToken) {
// Perform fetch call to API, passing reCAPTCHA token in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Recaptcha-Token': reCaptchaToken
},
body: JSON.stringify(/* form data converted to JSON */)
}).then(response => {
// Handle API response
})
})
})
}
</script>
<form id="demo-form">
<!-- Form data inputs ... -->
<br/>
<button id='submit'>Submit</button>
</form>
<script>
document.getElementById('submit').onclick = onClick
</script>
</body>
</html>
Once your web-app is converted to use the same API as your mobile app, there are few changes needed to add Approov. Add a function (fetchApproovToken
) to obtain an Approov token and then call this in place of the reCAPTCHA challenge invocation and include the obtained Approov token in the API request. The function fetchApproovToken
shows how to integrate Approov web protection in a single function using lazy (re-)initialization of Approov sessions:
<html>
<head>
<title>reCAPTCHA Demo</title>
<script src="https://www.google.com/recaptcha/api.js?
render=your-reCAPTCHA-site-key">
</script>
</head>
<body>
<script>
async function getRecaptchaV3Token() {
let recaptchaTokenPromise = new Promise(function(resolve, reject) {
try {
grecaptcha.ready(function() {
grecaptcha.execute('your-reCAPTCHA-site-key', {
action: 'submit'
}).then(function(recaptchaToken) {
resolve(recaptchaToken)
});
});
} catch (error) {
reject(error)
}
})
return recaptchaTokenPromise
}
// Fetches an Approov token and gets a fresh reCAPTCHA token if required
async function fetchApproovToken(api) {
let {Approov, ApproovSessionError} = await import("./approov.js")
try {
// Try to fetch an Approov token
let approovToken = await Approov.fetchToken(api, {})
return approovToken
} catch (error) {
if (error instanceof ApproovSessionError) {
// If Approov has not been initialized yet or the Approov session has
// expired, initialize and start a new session
await Approov.initializeSession({
approovHost: "web-1.approovr.io",
approovSiteKey: "your-Approov-site-key",
recaptchaSiteKey: 'your-reCAPTCHA-site-key',
})
// Get a fresh reCAPTCHA token
const recaptchaToken = await getRecaptchaV3Token()
// Fetch the Approov token
let approovToken = await Approov.fetchToken(api,
{recaptchaToken: recaptchaToken})
return approovToken
} else {
throw error
}
}
}
function onClick(event) {
event.preventDefault();
// Convert the form data to JSON
// ...
// Get an Approov token
fetchApproovToken("your-API-domain").then(approovToken => {
// Perform fetch call to API, passing Approov token in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Approov-Token': approovToken
},
body: JSON.stringify(/* form data converted to JSON */)
}).then(response => {
// Handle API response
})
})
}
</script>
<form id="demo-form">
<!-- Form data inputs ... -->
<br/>
<button id="submit">Submit</button>
</form>
<script>
document.getElementById('submit').onclick = onClick
</script>
</body>
</html>
The fetchApproovToken
function programmatically invokes a reCAPTCHA challenge if required and obtains an Approov token. The onClick
function is modified to call fetchApproovToken
and to include the obtained Approov token with the API request. Other examples provided in the reCAPTCHA documentation can also be adapted in a similar vein to the one described.
embed
token claim for reCAPTCHAYou may wish to interrogate the reCAPTCHA results in your backend API server. Google reCAPTCHA properties are added to the embed
claim of the Approov token which is a JSON object with a property called recap:your-reCAPTCHA-site-key
, where 'your-reCAPTCHA-site-key'
is the reCAPTCHA site key registered to your Approov account and used in the Approov token request. By default only the challenge timestamp and the domain properties are included in the token (using the same JSON structure as the full reCAPTCHA result), thus confirming that a reCAPTCHA lookup occurred. The following example shows the full decoded body of an Approov token with the default properties embedded from a reCAPTCHA request lookup:
{
"exp": 1620398492,
"ip": "1.2.3.4",
"arc": "EDZ2BABJIO",
"embed": {
"recap:your-reCAPTCHA-site-key": {
"challenge_ts": "2021-05-07T14:39:15",
"hostname": "<domain-of-the-calling-webpage>"
}
}
}
If the -embedResult
flag is used when you register the Google reCAPTCHA site and you have chosen to use encrypted tokens, JWEs, for the target API then the full results are included in the token. Note that the results from the Google reCAPTCHA lookup vary depending on the version. A slightly abbreviated example is shown below:
{
"exp": 1620653328,
"ip": "1.2.3.4",
"arc": "IACJXOWMRO",
"embed": {
"recap:your-reCAPTCHA-site-key": {
"success": true,
"challenge_ts": "2021-05-10T13:26:30Z",
"hostname": "<domain-of-the-calling-webpage>"
}
}
}
You should now be all set up to generate and receive web protection Approov tokens using Google reCAPTCHA. For the full reference on the web protection API and the more advanced features that are provided, please see the reference section after the integration summaries. For how to verify Approov tokens in your backend API server please see the section Backend Integration of the Approov Reference Guide and for information as to how to make use of the DPoP (Demonstrating Proof of Possession) token please refer to DPoP Token Use and Verification.
hCaptcha is a popular service to determine if a browser is being operated by a human. A browser request retrieves a token from the hCaptcha service which is then passed to the protected API as part of a request to be processed. A second request from the backend API obtains the full set of results associated with the token and uses them to determine whether to accept or reject the original request.
Before proceeding with an integration you must sign up for an account: hCaptcha signup. Each hCaptcha account has a secret and it can have one or more sites with each site having a unique site key. You should follow the Developer guide to either insert the hCaptcha widget, or use the invisible captcha approach. In either case, the instructions below will show how to add Approov web protection to the flow.
An hCaptcha site can be added by providing the hCaptcha site key with the associated secret for your account. You can add multiple sites: the site key is the unique key that identifies the target site for Approov web protection. The following command adds a site to your Approov account leaving all other settings at their default values:
approov web -hcaptcha -add your-hCaptcha-site-key -secret your-hCaptcha-secret -domain your-web-site-domain
This will output something like the following and requires confirmation:
WARNING: changing the web configuration will have an immediate impact in production
ATTENTION: If you wish to continue then please type YES and return: YES
hcaptcha site your-hCaptcha-site-key added
Listing all the configured web protection properties shows that the new site has been added:
approov web -list
This will output something like the following:
Site Key: your-Approov-site-key
Token Lifetime: 120 seconds
Refresh Token Lifetime: 7200 seconds
hCaptcha:
Optional: true
Site Key: your-hCaptcha-site-key
Domains:
your-web-site-domain
Min Score: 0.00
Include IP: false
Embed Result: false
Max Result Lifetime: 0s
The following table lists properties in order and provides their description:
Property Name | Description | |
Site Key | This is the Approov site key you need to use to identify your Approov account in web protection API requests | |
Token Lifetime | This is the lifetime given to Approov tokens obtained from the Approov web protection service. It may be changed using approov web -setTokenLifetime <seconds> . |
|
Refresh Token Lifetime | This specified the lifetime of the refresh token and determines the duration of an Approov session. The length of an Approov session is also limited by the maximum lifetime of any result obtained from hCaptcha's server (see Max Result Lifetime below). When the Approov session ends, any result obtained from hCaptcha's server must be renewed. The refresh token lifetime may be changed using approov web -setRefreshTokenLifetime <seconds> . |
|
hCaptcha | The heading that separates hCaptcha specific properties from general properties and those of other integrations. | |
Optional | Indicates whether it is optional to provide an hCaptcha result for the initial token fetch of an Approov session or not. This defaults to true which allows a valid Approov token to be provided for web protection using another configured integration even if an hCaptcha result is not provided. This property may be unset using approov web -hcaptcha -setRequired and reset using approov web -hcaptcha -setOptional . |
|
Site Key | Displays the hCaptcha site key used to configure the site and serves as the heading for properties related to just this site. Note that the hCaptcha secret is not listed; the secret is the private key for the site and it is never displayed or logged by Approov. If you use the hCaptcha dashboard to change the site key or secret, then you can always update the Approov site by adding it again with the new site key. | |
Domains | This lists the domains registered against the site; the entry is omitted if no domains were specified. Multiple domains can be registered by using the -domain flag multiple times. The backend hCaptcha lookup performed by Approov returns the domain that solved the hCaptcha challenge. If one or more domains are defined, then the returned domain must be found in this list. If no domains are registered, then a domain check is not performed. This facility can be used to selectively enable some of the domains that you have registered with the hCaptcha site definition. (We note that, when hCaptcha returns the domain, it strips the subdomain labels from the root. Please take care to ensure that the domains you register with Approov match the domains returned by hCaptcha results.) |
|
Min Score | The Score returned from an hCaptcha server-side lookup represents the probability that the user that solved an hCaptcha challenge is a human. Approov will return invalid tokens if the hCaptcha lookup returns a score lower than the configured minimum. The default, 0, will accept all scores. To change this value, use the -minScore <score> parameter when you add a site. |
|
Embed Result | If this is true *and you have chosen to use encrypted tokens*, JWEs, for the target API then the full hCaptcha result object will be included in generated Approov tokens. It defaults to false , use -embedResult when you add a site to turn it on. |
|
Max Result Lifetime | This specifies the maximum lifetime of any result obtained from hCaptcha's server, after which it must be renewed. It can be set by using the -maxResultLifetime <value> parameter when you add the subscription. A value of 0, the default, denotes that it is equal to the refresh token lifetime. The duration of an Approov session is determined by the smaller of the maximum result lifetime and the refresh token lifetime (see Refresh Token Lifetime above). | |
Rate Limit | This is not in the above list because no rate limit was specified, but it may be configured when you add a site to specify the maximum number of requests per minute: -rateLimit <limit> . Attempts to make requests beyond the limit will result in an error. The limit is specified in terms of the maximum number of requests per minute. If this is not specified, or set to 0, then no limit is imposed. |
To change a site’s properties you simply add it again with all the properties required for the change. Each addition of the same site key completely overwrites the previously stored entry.
Once the site is set up in Approov (and you’ve waited a couple minutes or so for the configuration to propagate), you can try retrieving an Approov token using the Approov web SDK. For the full documentation of the Approov web SDK please refer to the reference section. The web SDK can be downloaded using the Approov command line tool:
approov sdk -packageID approov.js.zip -getClientPackage approov.js.zip
This writes the latest available web SDK package to the approov.js.zip
file. Unzip the file, copy approov.js
into your project and load it as part of your web app.
Some example invocations of the Approov web SDK functions are provided below, where the api
argument is your Approov protected API domain and token
is the hCaptcha token obtained by calling the hcaptcha.getResponse()
function. Once an Approov session has been initialized, the first Approov token fetch must include a recent hCaptcha token, but this is not required for any subsequent Approov token fetches during the same Approov session:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize the Approov session
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
hcaptchaSiteKey: 'your-hCaptcha-site-key'
})
// ...
// Initial token fetch of Approov session provides the hCaptcha token
const approovTokenPromise = Approov.fetchToken(api, {hcaptchaToken: token})
// Subsequent token fetches during an Approov session don't require the hCaptcha
// token
const approovTokenPromise = Approov.fetchToken(api, {})
The Approov token should never be logged in production code.
The precise code required to obtain the hCaptcha token and then call the above function will depend on the your preferred approach for using hCaptcha. The key flow for using Approov is to first obtain an hCaptcha token, then pass it to Approov.fetchToken
before passing passing the resulting Approov token to your backend API. The simplest example provided for hCaptcha, hCaptcha’s Developer Guide, is to introduce a div
with h-captcha
class inside the form
to be protected:
<form>
...
<div class="h-captcha" data-sitekey="your-hCaptcha-site-key"></div>
...
</form>
In this approach, the example HTML doesn’t explicitly retrieve the hCaptcha token that is obtained, it is just embedded in the form data and submitted from there. To use Approov in the same style, you would need to ensure that the Approov token is also added to a hidden form input field before the form is submitted. The downside of this approach is that a user action is required (clicking the submit button) between the event that retrieves the approovToken and the form submission. This means that the Approov token lifetime is likely to be too short. Extending the token lifetime is not recommended unless network latency is causing token expiry before it can be processed by your backend API.
The example code below is taken from the hCaptcha Developer Guide and shows how to integrate hCaptcha into a web page so that when the captcha is solved successfully, a hidden hCaptcha token will be included with the form data.
<html>
<head>
<title>hCaptcha Demo</title>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
</head>
<body>
<form action="" method="POST">
<input type="text" name="email" placeholder="Email" />
<input type="password" name="password" placeholder="Password" />
<div class="h-captcha" data-sitekey="your-hCaptcha-site-key"></div>
<br />
<input type="submit" value="Submit" />
</form>
</body>
</html>
This approach does not permit the form submission to be intercepted for the purpose of including an Approov token with the form data. The solution is to override the form’s default submit behavior so that the Approov token fetch can be inserted in the flow. This approach requires that your javascript code constructs and issues the request to your API. This is best practice because you can then adjust the request to precisely match the one used by your mobile app - which is highly likely to include some properties in headers, notably the Approov token itself.
The first step for integrating Approov into this flow is to make the hCaptcha widget invisible and to modify the form’s default submit behavior to call a Javascript function (submitForm
) when the submit button is clicked. The submitForm
function programmatically invokes an hCaptcha challenge and includes the obtained hCaptcha token with the API request:
<html>
<head>
<title>hCaptcha Demo</title>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
</head>
<body>
<script>
function submitForm(event) {
event.preventDefault();
// Convert the form data to JSON
const data = {
email: document.getElementById("email").value,
password: document.getElementById("password").value
}
// Perform the challenge and get a hCaptcha token
hcaptcha.execute({async: true}).then(hCaptchaToken => {
// Perform fetch call to API, passing hCaptcha token in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Hcaptcha-Token': hCaptchaToken
},
body: JSON.stringify(data)
}).then(response => {
// Handle API response
})
})
}
</script>
<form>
<input id="email" type="text" name="email" placeholder="Email" />
<input id="password" type="password" name="password" placeholder="Password" />
<div class="h-captcha" data-sitekey="your-hCaptcha-site-key"
data-size="invisible"></div>
<br />
<button id="submit">Submit</button>
</form>
<script>
document.getElementById('submit').onclick = submitForm
</script>
</body>
</html>
Once your web-app is converted to use the same API as your mobile app, there are few changes needed to add Approov. Add a function (fetchApproovToken
) to obtain an Approov token and then use this in place of the hCaptcha challenge invocation and include the obtained Approov token in the API request. The function fetchApproovToken
shows how to integrate Approov web protection in a single function using lazy (re-)initialization of Approov sessions:
<html>
<head>
<title>hCaptcha Demo</title>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
</head>
<body>
<script>
// Fetches an Approov token and gets a fresh hCaptcha token if required
async function fetchApproovToken(api) {
let {Approov, ApproovSessionError} = await import("./approov.js")
try {
// Try to fetch an Approov token
let approovToken = await Approov.fetchToken(api, {})
return approovToken
} catch (error) {
if (error instanceof ApproovSessionError) {
// If Approov has not been initialized yet or the Approov session has
// expired, initialize and start a new session
await Approov.initializeSession({
approovHost: "web-1.approovr.io",
approovSiteKey: "your-Approov-site-key",
hcaptchaSiteKey: "your-hCaptcha-site-key",
})
// Perform the challenge and get a fresh hCaptcha token
const hcaptchaToken = await hcaptcha.execute({async: true})
// Fetch the Approov token
let approovToken = await Approov.fetchToken(api,
{hcaptchaToken: hcaptchaToken})
return approovToken
} else {
throw error
}
}
}
function submitForm(event) {
event.preventDefault();
// Convert the form data to JSON
const data = {
email: document.getElementById("email").value,
password: document.getElementById("password").value
}
// Get an Approov token
fetchApproovToken("shapes.approov.io").then(approovToken => {
// Perform fetch call to API, passing Approov token in a header
fetch("your-API-endpoint", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Approov-Token': approovToken
},
body: JSON.stringify(data)
}).then(response => {
// Handle API response
})
})
}
</script>
<form>
<input id="email" type="text" name="email" placeholder="Email" />
<input id="password" type="password" name="password" placeholder="Password" />
<div class="h-captcha" data-sitekey="your-hCaptcha-site-key"
data-size="invisible"></div>
<br />
<button id="submit">Submit</button>
</form>
<script>
document.getElementById('submit').onclick = submitForm
</script>
</body>
</html>
The fetchApproovToken
function programmatically invokes an hCaptcha challenge if required and obtains an Approov token. The submitForm
function is modified to call fetchApproovToken
and to include the obtained Approov token with the API request. Other examples provided in the hCaptcha documentation can also be adapted in a similar vein to the one described.
embed
token claim for hCaptchaYou may wish to interrogate the hCaptcha results in your backend system. hCaptcha properties are added to the embed
claim of the Approov token which is a JSON object with a property called hcap:your-hCaptcha-site-key
, where the your-hCaptcha-site-key
part is the hCaptcha site key registered to your Approov account and used in the Approov token request. By default only the challenge timestamp and domain attributes are included in the token (using the same JSON structure as the full hCaptcha result), thus confirming that an hCaptcha lookup occurred. The following example shows the full decoded body of an Approov token with the default properties embedded from a hCaptcha request lookup:
{
"exp": 1620398492,
"ip": "1.2.3.4",
"arc": "2VU2BFPIIO",
"embed": {
"hcap:your-hCaptcha-site-key": {
"challenge_ts": "2021-05-07T14:39:15",
"hostname": "<domain-of-the-calling-webpage>"
}
}
}
If the -embedResult
flag is used when you register the hCaptcha site and you have chosen to use encrypted tokens, JWEs, for the target API then the full results are included in the token. Note that the results from the hCaptcha lookup vary depending on your subscription level. A slightly abbreviated example is shown below:
{
"exp": 1620398895,
"ip": "1.2.3.4",
"arc": "BIFTVTMS6I",
"embed": {
"hcap:your-hCaptcha-site-key": {
"success": true,
"challenge_ts": "2021-05-07T14:45:58",
"hostname": "<domain-of-the-calling-webpage>"
}
}
}
You should now be all set up to generate and receive web protection Approov tokens using hCaptcha. For the full reference on the web protection API and the more advanced features that are provided, please see the following reference section. For how to verify Approov tokens in your backend API server please see the section Backend Integration of the Approov Reference Guide and for information as to how to make use of the DPoP (Demonstrating Proof of Possession) token please refer to DPoP Token Use and Verification.
The Demonstrating Proof-of-Possession (DPoP) proposed standard provides a way to constrain a bearer token so it can only be used by a particular client. It prevents any other party from using intercepted tokens by binding a token to a public key for which the corresponding private key is held by the client. When requesting an authorization token the client includes its public key, which the authorization server includes in the returned token. When using the authorization token, the client includes proof of possession of the private key in the request to the resource server, which can then verify the binding of the token to the key pair held by the client.
Approov uses the DPoP mechanism internally between the web SDK and the Approov service to protect their token fetch and refresh interactions. Optionally, the security of interactions between the client and the web API can be improved by also adopting this protocol.
The Approov web SDK provides a function, getDPoPToken
to generate and sign a DPoP token that can be included in a request alongside any Approov token, normally in the Dpop
header. Inclusion of this and checking it in your backend system provides confidence that the browser instance that requested the Approov token is the one that is making the request. By binding the Approov token to the DPoP token and by including the message hash, you enable your web API to check that the message was generated by the same browser instance that requested the Approov token and that the message was not modified in transit. By also including a nonce and checking its uniqueness in your web API, you can further protect against message replay attacks.
Example:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Ensure the Approov session is initialized
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
/* further web protection service specific arguments */
})
// ...
// Helper function to compute the SHA-256 hash of a string
async function sha256Hash(str) {
const uint8Arr = stringToUint8Array(str)
const hashBuffer = await window.crypto.subtle.digest('SHA-256', uint8Arr);
const result = new Uint8Array(hashBuffer)
return result
}
// Helper function to encode a byte array in base64-url format without padding
function uint8ArrayToBase64URL(uint8Arr) {
// Convert byte array to String (without char interpretation)
const str = String.fromCharCode.apply(null, uint8Arr)
// Convert to base64
const base64 = btoa(str)
// Convert to base64url without padding
const result = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
return result
}
try {
let nonce = new Uint32Array(8);
window.crypto.subtle.getRandomValues(nonce);
let approovToken = await Approov.fetchToken('your-Approov-protected-web-site',
{ /* web protection service request data */ })
let messageSignature = uint8ArrayToBase64URL(sha256Hash(requestBody))
// Generate a DPoP token
let dpopToken = await Approov.getDPoPToken('POST',
'https://' + 'your-Approov-protected-web-site',
uint8ArrayToBase64URL(nonce),
approovToken,
messageSignature)
// Include both the Approov token and the DPoP token in the request to your
// backend API
const response = await fetch('https://' + 'your-Approov-protected-web-site', {
method: 'POST',
headers: { 'Approov-Token': approovToken, 'Dpop': dpop },
body: requestBody
})
} catch (error) {
// Handle Approov session expiry and any other errors
}
For more details, see the SDK reference for getDPoPToken
.
In your backend system, in addition to checking the Approov token’s validity, you should also check that the DPoP token is valid and verify that the request message and the Approov token are consistent with the information included in the DPoP token:
htm
and htu
claims of the DPoP token.cnf
claim of the Approov token matches the public key thumbprint of the public key contained in the DPoP token’s jwk
claim.ath
claim of the DPoP token.msgs
claim, check that it matches the message hash of the request.Once your web app is receiving tokens from Approov you can start looking at live and accumulated activity using Approov metrics. A full description of the available features are covered in the associated documentation: Metrics Graphs. That section of the docs lists all the metrics that may be used and describes their meaning. Launch the metrics dashboard with the following CLI command: approov metrics
.
In short, there is a specific dashboard for live web protection based activity: Live: Web App Activity. Web protection metrics are also presented by the billing and summary dashboards: Billing Usage, Summary: Hourly Activity, Summary: Daily Activity, and Summary: Monthly Activity.
This section outlines the full specification of the Approov web protections interface. It presents the domains through which the service can be accessed; the Approov web SDK specifications; a description of how to use multiple web protection services in a single request; how to perform token binding; and how to troubleshoot problems.
The web protections service can be accessed from the following domains:
web-1.approovr.io
web-1.approovr.com
Both of these domains point to the same service instances some of which are located in the EU and others in the US.
The Approov web SDK is provided in a file called approov.js
as a module exporting a static Javascript object, called Approov
. Through this, any member functions and properties of the SDK can be accessed. The Approov web SDK can be downloaded using the Approov command line tool (see the installation instructions). Use the following command to download the latest web SDK package:
approov sdk -packageID approov.js.zip -getClientPackage approov.js.zip
This writes the latest available web SDK package to the approov.js.zip
file (or any path that you specify). Unzip the file and copy the resulting Approov web SDK Javascript file, approov.js
, into your project and load it as part of your web app:
Example:
<script type="module" src="/approov.js"></script>
<script type="module">
// Import the Approov SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Ensure the Approov session is initialized
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
/* further web protection service specific arguments */
})
// ...
// Call a function of the Approov SDK
Approov.fetchToken('your-Approov-protected-API-domain',
/* further web protection service specific arguments */)
.then(/* do something with the result */)
.catch(/* handle Approov session expiry and any other errors */)
// ...
initializeSession
initializes the Approov Javascript SDK’s single session with the Approov site key and the configuration data for at least one Web Protection Service. An Approov sessions does not last longer than the duration of the current browser session - there is no persistent storage via cookies.
Calling initializeSession again reinitializes the session, clearing all state associated with the old session. When calling initializeSession again without arguments, this creates a new session with the same configuration that was used on the previous initialization. initializeSession
throws an ApproovError
if any required arguments are missing.
static async initializeSession({
approovHost,
approovSiteKey,
fingerprintPublicAPIKey,
hcaptchaSiteKey,
recaptchaSiteKey
})
Parameter Name | Value Description |
---|---|
approovHost: string |
The address of the host through which to access the Approov service. One of the options listed in the section Domains. |
approovSiteKey: string |
Specifies the Approov site key to identify your Approov account to the web protection servers. Your Approov site key is listed as the first property output from a call to approov web -list . |
fingerprintPublicAPIKey: string |
Specifies the Fingerprint public API key for a Fingerprint subscription you have registered with Approov. Optional, but at least one of fingerprintPublicAPIKey, hcaptchaSiteKey, or recaptchaSiteKey must be provided. |
hcaptchaSiteKey: string |
Specifies the site key for an hCaptcha site you have registered with Approov. Optional, but at least one of fingerprintPublicAPIKey, hcaptchaSiteKey, or recaptchaSiteKey must be provided. |
recaptchaSiteKey: string |
Specifies the site key for a reCAPTCHA site you have registered with Approov. Optional, but at least one of fingerprintPublicAPIKey, hcaptchaSiteKey, or recaptchaSiteKey must be provided. |
Example:
// Initialize the session
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
fingerprintPublicAPIKey: 'your-Fingerprint-public-API-key',
hcaptchaSiteKey: 'your-hCaptcha-site-key',
recaptchaSiteKey: 'your-reCAPTCHA-site-key'
})
// ... some time later, refresh the session, keeping the configuration the same
await Approov.initializeSession({})
fetchToken
fetches a token for the requested API from the Approov service or retrieves a token from the Approov SDK’s internal cache if available and returns a Promise
for the Approov token. The function throws an error if the SDK is not initialized (ApproovSessionError
), a problem occurred during the token fetch (ApproovFetchError
), or an Approov token for the requested API is not available. The latter typically indicates that the Approov service has not been configured to provide tokens for the requested API. An ApproovSessionError
indicates that the session needs to be (re-)initialized and a new Approov token needs to be requested using a fresh set of web protection service results. An ApproovFetchError
is typically caused by an issue in communication with the Approov service - the web app should retry fetching the Approov token. Further details about errors are listed in section Troubleshooting Web Protection Errors.
On the first token fetch following the initialization of an Approov session, at least one of the fingerprintIDResult, hcaptchaToken, or recaptchaToken parameters must be provided in order for the Approov service to perform the relevant web security service lookup. On subsequent calls either the same parameter value can be provided or it can be omitted - in either case the Approov service will not reissue the web security service lookup. If the parameter provided differs from the one passed in the previous call to fetchToken
, the Approov service uses this to perform a fresh web security service lookup and then uses the new result from this in any further issued Approov tokens.
static async fetchToken(
api,
{
payload,
fingerprintIDResult,
hcaptchaToken,
recaptchaToken
}
)
Parameter Name | Value Description |
---|---|
api: string |
Specifies the domain of the target API. The api parameter must identify an API domain that is registered with your Approov account and has been web enabled. The CLI command, approov api -list will list the registered APIs and their properties, including if they are web enabled. |
payload: string | Uint8Array |
An optional parameter used for token binding on the web channel. The SHA-256 hash of the payload is computed and included as the pay claim of the fetched Approov token. See the section below on Web Protection Token Binding. |
fingerprintIDResult: Object |
Optional, an object with the properties fingerprintVisitorId - specifies the visitor ID returned by a Fingerprint identification request and fingerprintRequestId - specifies the request ID returned by a Fingerprint identification request. |
hcaptchaToken: string | Object |
Optional, specifies the token returned by an hCaptcha browser lookup. Either a string containing the hCaptcha token or an object containing the hCaptcha token in its response property and, optionally, any of the following properties: key - specifies the challenge key returned by the hcaptcha.getRespKey() API call, and host - specifies the root host domain that must match with the hCaptcha response. See the hCaptcha documentation. |
recaptchaToken: string |
Optional, specifies the token returned by a reCAPTCHA browser lookup. |
Example (using Fingerprint as the web protection service):
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// ...
// Initialize the Approov session and a Fingerprint agent at application startup
const fpPromise = Approov.initializeSession({
approovHost: 'your-Approov-service-domain',
approovSiteKey: 'your-Approov-site-key',
fingerprintPublicAPIKey: 'your-Fingerprint-public-API-key'})
.then(FingerprintJS.load({ token: 'your-Fingerprint-public-API-key' }))
// ...
// When you want to perform a request to your API server
try {
// Try to fetch an Approov token
let approovToken = await Approov.fetchToken('your-Approov-protected-API-domain', {})
// Pass the Approov token with the request to your API server
// ...
} catch (error) {
if (error instanceof ApproovSessionError) {
// If the Approov session has expired, initialize and start a new one
// using the same configuration as in the previous initialization
await Approov.initializeSession({})
// Perform a Fingerprint identification request before the Approov token fetch
let result = await (fpPromise.then(fp => fp.get()))
// Fetch the Approov token with the refreshed session and updated Fingerprint
// identification result
let approovToken = await Approov.fetchToken('your-Approov-protected-API-domain',
{fingerprintIDResult: result})
// Pass the Approov token with the request to your API server
// ...
} else {
// Handle or re-throw other errors
}
}
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize the Approov session
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
fingerprintPublicAPIKey: 'your-Fingerprint-public-API-key'
})
// ...
// Initial token fetch of Approov session provides the result of the Fingerprint
// identification request
const approovTokenPromise = Approov.fetchToken(api, {fingerprintIDResult: result})
// Subsequent token fetches during an Approov session don't require the result of
// the Fingerprint identification request
const approovTokenPromise = Approov.fetchToken(api, {})
getDPoPToken
generates a DPoP (Demonstrating Proof of Possession) JWT (JSON Web Token) using the current key-pair for the active Approov session. Including a DPoP token in a request to your backend API along with the Approov token, allows the backend system to check that the original requester of the Approov token is the same as the one making the request to your backend API. That is, the browser instance in possession of the crypto key that was used for both requests, thus proving that the Approov token is not being reused in a different environment. For more information on why and how to make use of the DPoP token mechanism please refer to DPoP Token Use and Verification.
static async getDPoPToken(method, uri, nonce, token, messageSignature)
Parameter Name | Value Description |
---|---|
method: string |
the HTTP method string for the request to be protected by the DPoP token. This is added to the DPoP token as its htm claim. |
uri: string |
the HTTPS URI value for the HTTP request to be protected by the DPoP token, ignoring any query and fragment parts. This is added to the DPoP token as its htu claim. |
nonce: any |
a nonce containing a unique (random) value to prevent replay attacks or a falsy value to skip addition of the nonce. Objects and arrays are allowed as well as base types. This is added to the DPoP token as its nonce claim. |
token: string |
a non-empty string whose hash is to be added to the DPoP token or a falsy value to skip addition of the token hash. The hash is generated using a call to SubtleCrypto.digest using the SHA256 algorithm, as mandated by the DPoP specification. This is added to the DPoP token as its ath claim. |
messageSignature: string |
a string containing a preconstructed signature or hash for the message. If the message signature is falsy, no message signature is added. The caller is free to use any implementation of their choice to provide the message signature to include in the DPoP token (for example following the Signing HTTP Messages proposal or including a base64URL-encoded SHA-256 hash of the message). This is added to the DPoP token as its msgs claim. |
Example:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Ensure the Approov session is initialized
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
/* further web protection service specific arguments */
})
// ...
// Helper function to compute the SHA-256 hash of a string
async function sha256Hash(str) {
const uint8Arr = stringToUint8Array(str)
const hashBuffer = await window.crypto.subtle.digest('SHA-256', uint8Arr);
const result = new Uint8Array(hashBuffer)
return result
}
// Helper function to encode a byte array in base64-url format without padding
function uint8ArrayToBase64URL(uint8Arr) {
// Convert byte array to String (without char interpretation)
const str = String.fromCharCode.apply(null, uint8Arr)
// Convert to base64
const base64 = btoa(str)
// Convert to base64url without padding
const result = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
return result
}
try {
let nonce = new Uint32Array(8);
window.crypto.subtle.getRandomValues(nonce);
let approovToken = await Approov.fetchToken('your-Approov-protected-web-site',
{ /* web protection service request data if required */ })
let messageSignature = uint8ArrayToBase64URL(sha256Hash(requestBody))
let dpopToken = await Approov.getDPoPToken('POST',
'https://' + 'your-Approov-protected-web-site',
uint8ArrayToBase64URL(nonce),
approovToken,
messageSignature)
// Include both the Approov token and the DPoP in the request to your backend API
const response = await fetch('https://' + 'your-Approov-protected-web-site', {
method: 'POST',
headers: { 'Approov-Token': approovToken, 'Dpop': dpop },
body: requestBody
})
} catch (error) {
// Handle Approov session expiry and any other errors
}
messageSignature
generates a signature for the message using the current Approov session’s key pair and returns a Promise
of ArrayBuffer
as its result. The signature algorithm and key used are the same as for generating a DPoP token. This provides a means for generating message signatures that can be verified using the public key contained in the DPoP token.
static async messageSignature(message)
Parameter Name | Value Description |
---|---|
message | an array (e.g. Uint8Array) or array buffer with the message to sign |
Example:
const message = "This is an unmodifiable message"
const signature = await messageSignature(new TextEncoder().encode(message))
The Approov SDK provides this facility to report the expiry time of the Approov session and the web protection service results. It enables a web-site to refresh these before they expire and so reduce the chance of an Approov web SDK call to fail because of an expired result and to minimize the impact of expiry events on the user experience.
getExpiries
provides the expiry time for the Approov session and the expiry times associated with all web protection service results as a Promise
of a map from subscription/site key to the respective expiry times as millisecond time stamps suitable for comparison with the current time as determined by calling Date().getTime()
. Example:
{"session":1663777376615, "fp:your-Fingerprint-public-API-key":1663780976615, "recap:your-reCAPTCHA-site-key":1663780976615}
static async getExpiries()
This function can be used to determine when web protection service results, or the Approov session, are about to expire and to provide new results or to refresh the Approov session before this happens.
Example - refreshing the Approov session:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Ensure the Approov session is initialized
await Approov.initializeSession({
approovHost: 'web-1.approovr.io',
approovSiteKey: 'your-Approov-site-key',
/* further web protection service specific arguments */
})
// ...
const timeLeft = (await Approov.getExpiries()).session - (new Date().getTime())
if (timeLeft < 300000) {
// Renew session if less than 5 minutes left
await Approov.initializeSession({})
// Note that the next Approov token fetch will require fresh web protection
// system results to be provided
}
Example - refreshing the Fingerprint check:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize Approov and a Fingerprint agent at application startup.
const fpPromise = Approov.initializeSession({
approovHost: 'your-Approov-service-domain',
approovSiteKey: 'your-Approov-site-key',
fingerprintPublicAPIKey: 'your-Fingerprint-public-API-key'})
.then(FingerprintJS.load({ token: 'your-Fingerprint-public-API-key' }))
// ...
try {
const expiries = await Approov.getExpiries()
const now = new Date().getTime()
for (const property in expiries) {
if (property.startsWith('fp')) {
let aFingerprintTimeLeft = expiries[property] - now
if (aFingerprintTimeLeft < 300000) {
// Refresh Fingerprint identification result if less than 5 minutes left
const fpGetResult = await fpPromise.then(fp => fp.get())
Approov.fetchToken('your-Approov-protected-API-domain',
{fingerprintIDResult: fpGetResult})
}
break
}
}
} catch (error) {
// Handle any errors
}
Although previous examples all focus on a single web protection service, the endpoints support the addition of parameters from multiple web protections in a single request. In this case, valid Approov tokens are only provided if the web protection checks pass for all parameters. An Approov.fetchToken
call that uses all the supported web protection services is provided below as an example:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize Approov session, Fingerprint, hCaptcha and reCAPTCHA
// ...
// Get web protection service results
const hcapToken = hcaptcha.getResponse()
const recapToken = grecaptcha.getResponse()
const fpGetResult = await fpPromise.then(fp => fp.get())
fpPromise
.then(fp => fp.get())
.then(fpGetResult => Approov.fetchToken('your-Approov-protected-API-domain',
{
fingerprintIDResult: fpGetResult,
hcaptchaToken: hcapToken,
recaptchaToken: recapToken
}))
.then(approovToken => {
// Perform the web API call adding the Approov token
})
.catch(err => {
// Handle Approov session expiry and any other errors
})
Approov Token Binding provides a mechanism for linking an Approov token with other properties that will be sent to you backend API, such as an User Authentication token. It is important not to use frequently changing data for binding as changing it invalidates all currently cached Approov tokens. The data is supplied to the Approov SDK as an Uint8Array. The SDK takes the array and calculates its SHA256 hash. This ensures that no Personal Identifiable Information (PII) can accidentally make it into the Approov service. The hash is then provided inside the Approov token in the pay
claim as a base64 encoded string. Existing backend Approov integrations for the mobile channel support this mechanism already. See the section on Token Binding for the mobile channel for further background.
The following snippet shows how to modify an Approov.fetchToken
call to use some provided data as a payload argument:
// Import the Approov web SDK
import { Approov, ApproovError, ApproovFetchError, ApproovServiceError,
ApproovSessionError } from '/approov.js'
// Initialize Approov session. Set up Fingerprint, hCaptcha or reCAPTCHA as
// required.
// ...
// Encode the data as Uint8Array
const payload = new TextEncoder().encode(data)
Approov.fetchToken('your-Approov-protected-API-domain', {payload: payload}))
.then(approovToken => {
// Perform the API call adding the Approov token
})
.catch(err => {
// Handle Approov session expiry and any other errors
})
The Approov web SDK’s fetchToken
function normally responds with a valid or invalid Approov token. However, if there is a problem with a request that prevents the normal web protection handler from completing, then an error response will be generated. Also, if the underlying calls to the integrated protection APIs fail with an error, then this error is propagated to the response instead of generating failed tokens.
In these cases an error object is thrown from the fetchToken
function:
Error Type | Description |
---|---|
ApproovError |
Subclass of Error , base for any specific error type thrown during any Approov web SDK call. |
ApproovSessionError |
The Approov session has not been initialized or has expired. If this occurs, a call to Approov.initializeSession({})
should be made to refresh the session. |
ApproovFetchError |
This wraps any error thrown by the fetch call made to perform a request to the Approov service. This
indicates that there was a communication or network issue when contacting the Approov service. The cause is
available in the errors property of the ApproovFetchError object. |
ApproovServiceError |
This represents an error reported by the Approov service, such as missing or malformed elements in the underlying
request. The errors property of the ApproovServiceError object contains more information
about the error, which can be useful for debugging. The possible error codes and descriptions are listed below. |
For ApproovServiceError
, the JSON object contains a sequence of key-value pairs, with the key indicating the type of error and the value providing extra information where appropriate. For some error types the value is just the empty string:
ApproovServiceError
{
"message": "<response-status-text>",
"errors": {
"<error-code-1>": "<further-details>",
"<error-code-2>": "",
...
}
}
An attempt is made to batch all problems with a request into the single error object to help make debugging quicker, however, this isn’t always possible. The following table lists the error keys with a description of their cause. Any extra data provided in the associated value may aid debugging.
Error Code | Description | |
---|---|---|
rqst-body-read-fail |
There was a problem reading the request body. | |
rqst-params-in-body-and-path |
The request contains parameters in the URL and in the message body; this is not permitted. | |
rqst-body-too-large |
The request message body is too large. | |
rqst-body-parse |
The request message body could not be decoded; it's probably not formatted correctly. | |
rqst-illegal-params |
The request included illegal parameters; the list of illegal entries is included in the value. | |
aprv-no-site-key |
Your Approov site key was not included in the request parameters. | |
aprv-no-web-protect-params |
The request didn't contain any web protection parameters; no parameters for Fingerprint, hCaptcha, or reCAPTCHA. | |
aprv-no-matching-account |
The specified Approov site key didn't match any account. | |
aprv-no-web-protect-def |
The specified Approov site key doesn't have any web protection integrations defined. | |
aprv-payload-param-invalid-size |
The request has a payload parameter, but it has an invalid size (32 bytes when decoded). It must be a Base64URL encoded SHA256 hash, see Web Protection Token Binding. |
|
aprv-payload-not-b64url |
The request has a payload parameter, but it isn't Base64URL encoded. It must be a Base64URL encoded SHA256 hash, see Web Protection Token Binding. |
|
aprv-api-no-targets |
The request parameters don't include any api target. |
|
aprv-api-empty-target |
The request parameters include an api target whose value is the empty string. |
|
aprv-api-duplicate-target |
The request parameters include more than one api entry with the same value. |
|
aprv-api-too-many-targets |
The request parameters include more api targets than there are APIs defined for the target account. |
|
aprv-api-not-found |
One or more api targets are not defined in your Approov account. |
|
aprv-api-not-web-enabled |
One or more api targets have not been web enabled. |
|
aprv-invalid-nonce |
The nonce required for the request is missing, malformed, not signed correctly or expired. | |
aprv-invalid-dpop-token |
The DPoP token header is missing, or the received DPoP token is malformed, not signed correctly or expired. | |
aprv-invalid-refresh-token |
The refresh token is missing, malformed, not signed correctly or expired. | |
fp-config-not-found |
Fingerprint parameters were used in the request but no Fingerprint subscriptions are defined for the account. | |
fp-missing-required-params |
The request doesn't have Fingerprint parameters, but a Fingerprint check is required by the account, see the section on Fingerprint configuration. | |
fp-param-mismatch |
Some of the parameters for a Fingerprint check are specified but others are missing. | |
fp-empty-browser-tok |
A request with Fingerprint parameters has the Fingerprint public API key parameter set to the empty string. | |
fp-empty-vis-id |
A request with Fingerprint parameters has the visitor ID parameter set to the empty string. | |
fp-empty-rqst-id |
A request with Fingerprint parameters has the request ID parameter set to the empty string. | |
fp-browser-tok-not-found |
The Fingerprint public API key specified in a request doesn't match any defined in the selected Approov account. | |
fp-tok-check |
There was an error performing the Fingerprint server-side check; the error string is included in the value. | |
hcap-config-not-found |
hCaptcha parameters were used in the request but no hCaptcha Sites are defined for the account. | |
hcap-missing-required-params |
The request doesn't have hCaptcha parameters, but an hCaptcha check is required by the account settings, see the section on hCaptcha configuration. | |
hcap-param-mismatch |
Some of the parameters for an hCaptcha check are specified but others are missing. | |
hcap-empty-site-key |
A request has an hCaptcha site key parameter set to the empty string. | |
hcap-empty-token |
A request has an hCaptcha token parameter set to the empty string. | |
hcap-site-key-not-found |
The hCaptcha Site key specified in a request doesn't match any defined in the selected Approov account. | |
hcap-token-check |
There was an error performing the hCaptcha server-side check; the error string is included in the value. | |
recap-config-not-found |
reCAPTCHA parameters were used in the request but no reCAPTCHA Sites are defined for the account. | |
recap-missing-required-params |
The request doesn't have reCAPTCHA parameters, but a reCAPTCHA check is required by the account settings, see the section on reCAPTCHA configuration. | |
recap-param-mismatch |
Some of the parameters for a reCAPTCHA check are specified but others are missing. | |
recap-empty-site-key |
A request has a reCAPTCHA site key parameter set to the empty string. | |
recap-empty-token |
A request has a reCAPTCHA token parameter set to the empty string. | |
recap-site-key-not-found |
The reCAPTCHA Site key specified in a request doesn't match any defined in the selected Approov account. | |
recap-token-check |
There was an error performing the reCAPTCHA server-side check; the error string is included in the value. |