SDK Identity Verification
Learn how to use Appcues SDK Identification Verification to verify your user identities and secure your published content.
Table of Contents
Introduction
Identity Verification lets you digitally sign any user data sent to Appcues to ensure only authorized users can access your Appcues experiences. Implementing Identity verification ensures 3rd parties cannot pretend to be other users and access any Appcues experiences that may contain sensitive information such as PII.
Implementing Identity verification requires two main steps:
- One of your product's engineers adds code to generate signatures for your user IDs and provides those signatures to the Appcues SDKs you use (e.g. JavaScript or Mobile).
- When you’re confident that your websites and mobile apps that use the Appcues SDK include a signature, you can enable Enforcement Mode to prevent Appcues from returning any experiences if a signature is absent. Since 3rd parties don't have access to the signature you provided in Step 1, they cannot access Appcues experiences.
Technical Requirements
Implementing Identity Verification will require the assistance of one of your team’s engineers. The work involves issuing SDK keys, generating signatures on your product’s backend servers, and sending the signature to Appcues to the Appcues SDKs (JavaScript and/or mobile) wherever they are used.
To implement SDK Identity Verification
As a software developer on your companies product, you will need to:
- Generate a User ID signature on your backend server. The Appcues SDK will use this signature to verify that users can only see content intended for their user ID.
- Deliver that User ID signature to your website or mobile app, where the Appcues SDK is installed.
- Provide the User ID signature to the Appcues SDK using the instructions below.
Issuing a secret key
To create an SDK secret for signing Appcues user IDs, use the SDK Authentication Keys endpoints documented in our API.
Generating the User ID signature
To generate a User ID Signature, you must be able to sign a JWT from your application's backend server. Use the SDK Secret we provide to sign a JWT containing your account ID, user ID, and an expiration date for the signature. We have included code examples in several languages below to generate the User ID signature.
Once the signature is generated, you must get it into the Appcues SDK in your web or mobile application. The method for this will vary greatly from customer to customer, but in general, if you’re using server-side rendering, you should plan to embed the signature into the page. Otherwise, you should make an authenticated API call to your backend server to retrieve the signature.
Protect your SDK Secret!
Do not include the SDK secret directly in your front-end code. Doing so will compromise the integrity of the SDK secret and make it possible for an attacker to forge a signature and retrieve the personalized content intended for your users.
Code samples to generate User ID Signatures
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
Appcues JavaScript SDK
Add a JavaScript snippet before the user is identified to provide a User ID Signature to our web SDK. The steps will be identical whether Appcues is installed directly or through Segment.
- Put the code we provide on your servers to return the signature needed.
- Assign the User ID Signature to
window.AppcuesSettings.userIdSignature
.
if (window.AppcuesSettings) {
window.AppcuesSettings.userIdSignature = signature;
} else {
window.AppcuesSettings = { userIdSignature: signature };
}
// Appcues.identify()
Set the signature before calling any SDK methods
You must provide the signature before any calls to the Appcues SDK are made. If calls are made to Appcues.track()
, Appcues.page()
, or any other SDK methods before a signature is provided, the Appcues SDK may try to connect to our servers without a signature. If Enforcement Mode is enabled, then that connection will fail.
Note: If you are installed with Segment, you may have calls to Analytics.track()
or Analytics.page()
. These calls are equivalent to calls to the Appcues SDK, and the signature must also be specified before these calls.
Appcues Mobile SDKs
- Pass a user property called
appcues:user_id_signature
on theidentify(userId, props)
call using any Appcues mobile SDK version>=1.4.0
. - For anonymous users, prefix the user ID with
anon:
in Appcues mobile SDKs version>=1.4.0
.Note that it will no longer be possible to send Appcues profile attributes for anonymous users through the mobile SDKs as of version>= 2.0
.
appcues.identify(userId, mapOf("appcues:user_id_signature" to signature))
appcues.identify(userID: userID, properties: ["appcues:user_id_signature": signature])
Testing SDK Identity Verification
When you send an invalid signature to the Appcues API, the request will be rejected, even if Enforcement Mode is disabled. This allows you to test the validity of the signatures in an app without impacting content delivery for your existing users or your other applications.
You can enable Enforcement Mode if you’ve provided a signature and still see Appcues content.
If you’ve provided a signature and do not see the content you expect, check out the Troubleshooting section of this document.
What happens if the signature is missing
If Enforcement Mode is disabled (the default) and you do not provide a signature, you will still see Appcues content. This can happen unexpectedly with the Appcues JavaScript SDK if it connects to Appcue's API servers before the signature is provided.
To test for this scenario, you can window.APPCUES_FORCE_SENDER_VERIFICATION = true
to cause the Appcues JavaScript SDK to fail to connect to our API if no signature is provided.
Enforcing Signatures
Once your testing is complete, you can enable Enforcement Mode. With Enforcement Mode enabled, retrieving Appcues content without providing a valid signature will no longer be possible. Therefore you must ensure that signatures are provided everywhere Appcues content is shown.
To enable Enforcement Mode, you must get an API Key and API_Secret for our Public API from an admin of your Appcues account. If you are an Appcues admin, you can issue an API key and secret yourself from your API Keys settings page.
With an API_KEY and API_SECRET, you can enable Enforcement Mode with the following curl
command (don’t forget to replace ACCOUNT_ID
with your account ID and your API_KEY and API_SECRET with what you got from your Appcues administrator!)
curl <https://api.appcues.com/v2/accounts/ACCOUNT_ID/enforcement_mode/enable> \\
-u API_KEY:API_SECRET \\
-d ''
If you suspect something is wrong and you want to roll back, you can quickly disable Enforcement Mode by changing enable
to disable
in the above command:
curl <https://api.appcues.com/v2/accounts/ACCOUNT_ID/enforcement_mode/disable> \\
-u API_KEY:API_SECRET \\
-d ''
Refer to the SDK Authentication Keys section of our Public API docs for more details on using our API to control SDK Identity Verification.