Identity Verification
Learn how to use Identity Verification to ensure only users you verify can access their Appcues experiences.
Table of Contents
Introduction
Identity Verification lets you digitally sign the UserID sent to Appcues when identifying users to ensure only users you verify can access their Appcues experiences. Implementing Identity verification ensures 3rd parties cannot pretend to be other users and see Appcues experiences not intended for them.
Implementing Identity verification requires three steps to be performed by one of your developers:
- Add code to your product's backend to generate a signature for any UserID and provide that signature to the Appcues JavaScript SDK and/or Mobile SDKs.
- Test and verify that valid signatures are provided when making calls to the Appcues SDK, across all your websites or mobile apps that use Appcues.
- Once verified, you can enforce identity verification by enabling Enforcement Mode to reject any Appcues SDK calls that do not contain valid signatures.
That is it!
To set up Identify Verification (Developers)
As a software developer for your product, you will need to:
- Generate a UserID signature on your backend server. The Appcues SDK will use this signature to verify that users can only see content intended for their UserID.
- Deliver that UserID signature to your website or mobile app.
- Provide the UserID signature before making any calls to the Appcues SDK using the instructions below.
Generating the UserID signature
Add the appropriate code to your product's backend server using one of the backend code samples at the bottom of this page for a guide.
Once the signature is generated, you must securely provide it to the Appcues SDK in your web or mobile application or when using the Appcues HTTP API or Appcues Launchpad as described below. If your product uses server-side page rendering, you should embed the signature into the page delivered to the browser. Otherwise, make an authenticated API call to your backend server to retrieve the signature.
By default, signatures are valid for 15 minutes after generating them. If you have a long-running single-page web app or mobile app, this could result in calls to the SDK failing after 15 minutes. If your app must call the Appcues SDK calls more than 15 minutes after obtaining a valid signature, you can either
- change the time to longer than 15 minutes in the appropriate code sample provided below
- request an updated signature from your backend server
Set the signature before making calls to the Appcues SDK
It is important to provide the signature before making any calls to the Appcues SDK. If Enforcement Mode is enabled while calls are made to Appcues.track()
, Appcues.page()
, or any other Appcues SDK methods before providing a signature, data will not be sent to Appcues' servers, and experiences will not be shown. As soon as a valid signature has been set, data will be sent to Appcues' servers to qualify and show Appcues experiences. If the signature is never set and Enforcement Mode is on, the user will not see Appcues experiences across any mobile or web apps using the Appcues SDK for that account.
Note: Different behavior will occur when Enforcement Mode is off (the default), as described in the Testing Identity Verification section below.
Note: If using Segment.io, the signature must be specified before calling Segment too.
Provide the Signature to the Appcues Javascript SDK
The process is the same if Appcues is installed directly or through Segment, Rudderstack, or any other third-party tool.
Assign the UserID Signature to window.AppcuesSettings.userIdSignature
as follows.
// retain any Appcues SDK settings that may have been set prior
if (window.AppcuesSettings) {
window.AppcuesSettings.userIdSignature = signature;
} else {
window.AppcuesSettings = { userIdSignature: signature };
}
// Your Appcues.identify() call, or equivalent call for Segment, Rudderstack, etc. goes below
Provide the Signature to the Appcues Mobile SDK
Pass a user property called appcues:user_id_signature
on the identify(userId, props)
call. Note this requires Appcues mobile SDK version >= 1.4.0
.
# Android / Kotlin
appcues.identify(userId, mapOf("appcues:user_id_signature" to signature))
# iOS / Swift
appcues.identify(userID: userID, properties: ["appcues:user_id_signature": signature])
Provide the Signature to Appcues Launchpad
If you are using the Appcues Launchpad Custom Install, you need to provide the signature as described in the Request Headers section Appcues Launchpad Custom Install documentation. Note: If using the standard (not custom) Launchpad installation, you do not need to provide the signature.
Testing Identity Verification
While Enforcement Mode is disabled (the default), the following behavior will occur:
See the next section “Enforcing Identity Verification” for what happens when Enforcement Mode is enabled.
- If no signature is provided to the SDK OR a valid signature is provided to the SDK:
- Calls to the SDK will work, and Experiences will be shown to users according to the targeting settings of your Appcues Flows and other experiences.
- If an invalid signature is provided to the SDK:
- Calls to the SDK will fail, and Experiences will not be shown to users. Ensure the signature you provided to the SDK is valid according to the instructions in ‘Generating the UserID signature.’
To locally test web apps if the signature is missing while Enforcement Mode is disabled:
If Enforcement Mode is disabled (the default) and you do not provide a signature, the user will still see Appcues experiences. To locally test for this scenario with web apps, add the following line of code before your first call to the Appcues JavaScript SDK.
// Allow testing Identity Verification locally with console messages
window.APPCUES_TEST_IDENTITY_VERIFICATION = true;
When window.APPCUES_FORCE_IDENTITY_VERIFICATION = true
you will see one the following messages in the JavaScript console, after every call to the Appcues SDK.
-
Appcues Identity Verification Testing: Successfully sent update to API using User ID Signature
- This console message means that a valid signature was provided before a call to the SDK
-
Appcues Identity Verification Testing: Failed to send update to API - verification failure: No User ID Signature present
- This console message means a signature was not provided before a call was made to the Appcues SDK and an Experience was not shown.
-
Appcues Identity Verification Testing: Failed to send update to API - verification failure: Invalid User ID Signature NNNN
- This console message means an invalid signature was provided. Check to ensure the signature is valid according to the instructions in ‘Generating the UserID signature.’
Important: After testing is done, be sure to remove the window.APPCUES_FORCE_IDENTITY_VERIFICATION = true
statement before deploying your app to production to not display these log messages to your user's JavaScript console.
Enforcing Identity Verification
Once your testing is complete across all your web and mobile apps that use the Appcues SDK with your Appcues account, you can enable Enforcement Mode to only show Appcues experiences to users with a valid signature.
Important: Enabling Enforcement Mode for your account affects all of your mobile or web apps in that account. Do not enable Enforcement Mode until you have tested both your mobile and web apps, and ensure that all of your end users are using a version of your mobile or web app that is correctly performing identity verification, as described above. For mobile apps, after you release a version of your app to the store with identity verification, you'll need to target your Appcues flows to app versions at or above this new version, or require your users update their apps to the latest version to continue usage. You should plan to enable Enforcement Mode in your account only after users in your apps have had a chance to update to this required version.
After Enforcement Mode is enabled, the following behavior will occur for all mobile and web apps using the Appcues SDK in your account. See the previous section for what happens when Enforcement Mode is disabled:
- If a valid signature is provided to the SDK:
- Calls to the SDK will work, and Experiences will be shown to users according to the targeting settings of your Appcues Flows and other experiences.
- If no signature is provided to the SDK OR an invalid signature is provided to the SDK:
- Calls to the SDK will fail, and Experiences will not be shown to users. Ensure the signature you provided to the SDK is valid according to the instructions in ‘Generating the UserID signature.’
To enable Enforcement Mode:
- Get an API Key and API_Secret for your Appcues account. If you are an Appcues admin, you can issue an API key and secret yourself from the API Keys settings page. For more details on using our API to control Identity Verification, see the Public API's SDK Authentication Keys page.
- Enable Enforcement Mode with the following
curl
command. ReplaceACCOUNT_ID
with your account ID and your API_KEY and API_SECRET with the values from step 1.
curl <https://api.appcues.com/v2/accounts/ACCOUNT_ID/enforcement_mode/enable> \\
-u API_KEY:API_SECRET \\
-d ''
- Perform a final check of all your web and mobile apps that use the Appcues SDK with your account to verify Appcues experiences are being shown as expected, according to your experience's targeting settings. Appcues experiences should show if the steps in Testing Identity Verification were followed. You are done!
If your Appcues experiences do not appear as expected, no worries, just disable Enforcement Mode.
To disable Enforcement Mode:
Change enable
to disable
in the command, as shown below. Replace ACCOUNT_ID
with your account ID and your API_KEY and API_SECRET with the values you used when you enabled it as described above.
curl <https://api.appcues.com/v2/accounts/ACCOUNT_ID/enforcement_mode/disable> \\
-u API_KEY:API_SECRET \\
-d ''
Important: The API key and API secret generated in your account's dashboard are separate from the SDK authentication key used for generating the user ID signature. The key/secret pair is used for interfacing with our public API, which allows you to enable or disable enforcement mode. The public API also provides endpoints for generating a separate SDK authentication key specific to identity verification. This SDK key is then used in the following code for generating the actual user ID signatures for your users.
Code samples to generate a UserID Signature
Each code sample requires the following information to generate a signature valid for 15 minutes.
- Your account ID - can be found in Appcues Studio in the Settings > Account tab
- The current user's UserID - the same UserID provided to the Appcues Identity call
- A Secret from the Appcues Public API SDK Authentication Keys. IMPORTANT: It is essential to keep the secret confidential and not in client-side JavaScript code. Disclosure of the secret would allow a hacker to impersonate another UserID and see their Appcues Experiences. Note that the SDK authentication key is separate from the API key and secret generated via your Appcues's account's dashboard.
If your product's backend uses a language other than the ones listed below, please contact Appcues support for assistance.
C#
Using the Jose-jwt library
using System;
using System.Collections.Generic;
using Jose;
using System.Text;
static class AppcuesSignature
{
public static string CreateToken(string userId, string accountId, byte[] secretKey)
{
var payload = new Dictionary<string, object> {
{ "iss", accountId },
{ "sub", userId },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(15).ToUnixTimeSeconds() }
};
return Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
}
}
Elixir
Using the Joken library
@spec create_token(binary(), binary(), binary()) :: nil | binary()
def create_token(account_id, user_id, sdk_secret)
expiry = (Timex.now() |> Timex.to_unix()) + (1 * 60 * 60)
claims = %{
"iss" => account_id,
"sub" => user_id,
"exp" => expiry # The timestamp when this JWT should expire
}
signer = Joken.Signer.create("HS256", sdk_secret)
case Joken.encode_and_sign(claims, signer) do
{:ok, token, _} -> token
{:error, _} -> nil
end
end
Go
Using the github.com/dgrijalva/jwt-go
library
import (
"github.com/dgrijalva/jwt-go"
"time"
)
func CreateToken(accountID string, userID string, secretKey []byte) (string, error) {
// Create the Claims
claims := jwt.StandardClaims{
Issuer: accountID,
Subject: userID,
ExpiresAt: time.Now().Add(time.Minute * 15).Unix(),
}
// Create the token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// Sign and get the complete encoded token as a string
return token.SignedString(secretKey)
}
Javascript
Using the Jose library
const jose = require("jose");
function createToken(account_id, user_id, secretKey) {
const secret = new TextEncoder().encode(secretKey);
const signature = new jose.SignJWT({})
.setProtectedHeader({ alg: 'HS256' })
.setIssuer(account_id)
.setSubject(user_id)
.setExpirationTime('1h')
.sign(secret);
return signature; // returns a promise
}
Java
Using the auth0-jwt library
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.JWT;
import java.util.Date;
class AppcuesSignature {
public static String CreateToken(String accountId, String userId, byte[] secret) {
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withIssuer(accountId)
.withSubject(userId)
.withExpiresAt(new Date(System.currentTimeMillis() + 15 * 60 * 1000))
.sign(algorithm);
return token;
}
}
PHP
Using the php-jwt library
use \\Firebase\\JWT\\JWT;
function create_token($user_id, $account_id, $secret_key) {
$payload = array(
"iss" => $account_id,
"sub" => $user_id,
"exp" => time() + 15 * 60
);
$jwt = JWT::encode($payload, $secret_key, 'HS256');
return $jwt;
}
Python
Using the PyJWT library
import jwt
import datetime
def create_token(account_id, user_id, secret_key):
# Create the Claims
claims = {
'iss': account_id,
'sub': user_id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
}
# Create the token
token = jwt.encode(claims, secret_key, algorithm='HS256')
# Sign and get the complete encoded token as a string
return token
Ruby
Using the ruby-jwt library
require 'jwt'
def create_token(user_id, account_id, secret_key)
claims = {
iss: account_id,
sub: user_id,
exp: Time.now.to_i + 15 * 60
}
JWT.encode(claims, secret_key, 'HS256')
end