iAdvize Authenticated Messaging - web backend implementation

The visitor authentication process consists of a creation of a signed and encrypted token forged on your backend and forwarded to the iAdvize tag through your frontend (website) in order iAdvize to be able to recognize the visitor and display relative visitor and conversational data accordingly.

 

1. Important information and recommendations


1.1 User identifier

 

It should be unique per user - the user ID cannot be recycled from one user to another.

It should be max 255 characters.

If you don’t respect these guidelines, iAdvize will consider all visitors as one and the same visitor. We will then associate all the conversations of visitors with the same user ID. This creates a confidentiality issue: visitors will then have access to the content of each-other's conversations, including text and attachments.

 

1.2 Token encryption

 

When you generate a JWE which contains your user identifier, your library to generate this token should support A256GCM and RSA_OAEP_256 for creating the JWE. The inner JWS must be signed with RS256.

 

1.3 Private Key storage

 

We store our private key using an external security tool call Vault, so our private key is not exposed through our code or any database access.

 

1.4 About the External ID usage (extID)

 

The visitor authentication system fully replaces the usage of the "ExtID". Then, if you use the visitor authentication system in an authenticated space of your website, you have to ensure that you are not using the "ExtID" system in parallel.

 

1.5 Sending visitor data in the JWT token

 

In addition to the userId claim, an optional visitorData claim can be added to the JWT. This is how it would look like, before encryption : 

 

 Encoded  Decoded
eyJhbGciOiJSUzI1NiJ9.eyJodHRwczpcL1wvaWFkdml6ZS5jb21cL
3VzZXJJZCI6InRlc3RfZG9jdW1lbnRhdGlvbiIsImh0dHBzOlwvXC9
pYWR2aXplLmNvbVwvdmlzaXRvckRhdGEiOnsiY291bnRyeSI6IkZyY
W5jZSIsImZpcnN0TmFtZSI6IkphbmUiLCJsYXN0TmFtZSI6IkRvZSI
sInppcENvZGUiOiI0NDAwMCIsImFkZHJlc3MiOiI5IHJ1ZSBOaW5hI
FNpbW9uZSIsInBob25lTnVtYmVyIjoiKzMzNjUxMjI5ODU2IiwiY2l
0eSI6Ik5hbnRlcyIsImVtYWlsIjoiamFuZS5kb2VAZW1haWwuY29tI
n0sImlzcyI6Imh0dHBzOlwvXC90ZXN0LmlhZHZpemUuY29tIiwiZXh
wIjoxNjkxNTg4NzA3fQ.YrR0AisAbXzdcF7IGdKb4DGR0JOudaBS5E
s78YW_K3x65WfGlQhktYlgKud0AH8AgVi7EDb7aAWy5-9kuwezuqnL
CBBsaUBWJSkSN2OxVh0tSylNEKPIOYRlEG2lS6Fwlo_UdFkKQ1SIBG
jSEcPqepVwO58od6GlY5yjcTlOF6dj7RyON4KRxRir0wP6yCbZi2oa
4IS_beilJvS9ymZO-8zRnGHKS-J_xqqhpTkz8lF11Wb0UQz1ML16nq
uTIHLTzYO4e5UqdK0BUCIe0ivla6r5YQR5HYYhCKssvycqFdYh4mWF
lSziFkB-HKxbWCz-qbugkxvMicTXvEzwO-fELg
{
  "https://iadvize.com/userId": "test_documentation",
  "iss": "https://test.iadvize.com",
  "https://iadvize.com/visitorData": {
    "country": "France",
    "firstName": "Jane",
    "lastName": "Doe",
    "zipCode": "44000",
    "address": "9 rue Nina Simone",
    "phoneNumber": "+33651229856",
    "city": "Nantes",
    "email": "jane.doe@email.com"
  },
  "exp": 1690376935
}

 Detail here


 

 

It may contain the following fields, all optional strings :

 

address
city
country
email
firstName
lastName
phoneNumber
zipCode

 

These fields will then be displayed on the desk of the agent :

 

Capture d’écran 2024-05-15 à 09.58.59.png

 

See the 3. Technical Backend Implementation section.

 

2. Signature and Encryption Detailed Process


2.1 Key pair generation and sharing

 

lD2vXG5Y.png

 

You will need to generate a key pair: a public key/private key. You will have to share your public key with iAdvize.
iAdvize provides you with its public key.

One way to generate your keys (you can adjust the number of bits):

 

openssl genpkey -out rsaPrivateKey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048
openssl rsa -in rsaPrivateKey.pem -pubout -out rsaPublicKey.pem

 

2.2 Sign and encrypt

 

YfLN9KHI.png

 

iAdvize will make the process the other way around which consist of decrypting the JWE (using iAdvize Private key) and verify the token signature (using Customer Public key) in order to finally extract the User ID and create a Visitor Authenticated Session (a new JWS generated by iAdvize internally):

 

pnLWCZHY.png

 


2.4 iAdvize Public key (use for Production)

 

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1KdAzuUa5rOXgLHavoDRYoNXzwWz/p
FhgGypYFbvV8DNjB93XK2AzKTwW+vxT7RYl4f+sKLdEi3dJYgPt2hquhTNmFxAzRvTuolUOKr1XN
x7QbDj+7cfLVDYjmds/ydNtyHi8TUHSvfzs8SGXO5E5H13llmayPEslHKShG0cLIDcLNr6hJcfv9fvO
ZqQlLQ4Bx7to/66IHke9zY+1oidrUdFGzxXG+RGK81mIMuXj6N2EGJ7YYcQqXJfBJnWFlSGCQNtt
w5Rfj00eZbkMRO3XohhNqGIiBG2tejSjfB53UpiHdbzni+tyB72R5aaq4d+gkkgaOVYn/Or2fArOH2
FUQIDAQAB

 

3. Technical Backend implementation

 

Note: You will have to define a custom claim which will be embedded in the signed token (JWS). You have to prefix all the claims with https://iadvize.com/

 

⚠️ The claim https://iadvize.com/visitorData is optional ⚠️

 

3.1 JWT library

 

First, to deal with JWT, you will have to choose the right library which fits to your backend language.
You can find on the official JWT website a list of many libraries implemented for many languages.
The chosen library should support "A256GCM and RSA_OAEP_256" for creating the JWE, the inner JWS must be signed with "RS256".

 

3.2 An example in SCALA

 

Here you have a technical implementation of the solution in SCALA :

 

import java.security.interfaces.RSAPublicKey
import java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec}
import java.security.{KeyFactory, KeyPairGenerator, PrivateKey, PublicKey}
import java.util.Date
import com.nimbusds.jose._
import com.nimbusds.jose.crypto._
import com.nimbusds.jwt.{JWTClaimsSet, SignedJWT}

object JWEBuilder {
	def main(args: Array[String]): Unit = {
		val (clientPubKey, clientPrivateKey) = getClientKeys()
		val (iadvizePubKey, iadvizePrivateKey) = getIAdvizeKeys()
		val JWS1 = createJWS(clientPrivateKey)
		val JWE1 = createJWE(iadvizePubKey, JWS1)
		println(s"JWS : ${JWS1.serialize()}")
		println(s"JWE : ${JWE1.serialize()}")
		val token = JWE1.serialize()
		val JWS2 = decryptJWE(iadvizePrivateKey, token)
		println(s"Is valid JWS : ${JWS2.verify(new RSASSAVerifier(clientPubKey.asInstanceOf[RSAPublicKey]))}")
		println(s"${JWS2.getJWTClaimsSet}")
	}
	def getClientKeys() : (PublicKey, PrivateKey) = {
		val generator = KeyPairGenerator.getInstance("RSA")
		generator.initialize(2048)
		val pairClient = generator.generateKeyPair
		val pubKeyClient = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pairClient.getPublic.getEncoded))
		val privateKeyClient = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(pairClient.getPrivate.getEncoded))
		(pubKeyClient, privateKeyClient)
	}
	def getIAdvizeKeys() : (PublicKey, PrivateKey) = {
		val generator = KeyPairGenerator.getInstance("RSA")
		generator.initialize(2048)
		val pairIadvize = generator.generateKeyPair
		val pubKeyIadvize = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(pairIadvize.getPublic.getEncoded))
		val privateKeyIadvize = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(pairIadvize.getPrivate.getEncoded))
		(pubKeyIadvize, privateKeyIadvize)
	}
	def createJWS(clientPrivateKey: PrivateKey) : SignedJWT = {
		val claimsSet = new JWTClaimsSet.Builder()
		// You can define custom claims. Here you can define your User ID which will be embed in the signed token(JWS).
		// You have to prefix all the claims with `https://iadvize.com/”.
		// The claim https://iadvize.com/userId is mandatory.
		claimsSet.claim("https://iadvize.com/userId","c42ab96d-0637-4d1e-8be3-0a872d9d1ef1")
		// For security reason it’s better to set a quick expiration time. As this token will just be used to initialise a new secured visitor session on iAdvize 1 minute seems a good duration.
		claimsSet.expirationTime(ZonedDateTime.now().plusMinutes(1))
		val signer = new RSASSASigner(clientPrivateKey)
		val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet.build())
		signedJWT.sign(signer)
		signedJWT
	}
	def createJWE(iadvizePublicKey : PublicKey, jws : SignedJWT) : JWEObject = {
		val header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM).build()
		val payload = new Payload(jws)
		val jwe = new JWEObject(header, payload)
		jwe.encrypt(new com.nimbusds.jose.crypto.RSAEncrypter(iadvizePublicKey.asInstanceOf[java.security.interfaces.RSAPublicKey]))
		jwe
	}
	def decryptJWE(iadvizePrivateKey: PrivateKey, token : String): SignedJWT = {
		val o = JWEObject.parse(token)
		o.decrypt(new RSADecrypter(iadvizePrivateKey))
		o.getPayload.toSignedJWT
	}
}

 

The key parts for you are the functions createJWS() and createJWE() which we will detail below.

createJWS()

 

def createJWS(yourPrivateKey: PrivateKey) : SignedJWT = {
	val claimsSet = new JWTClaimsSet.Builder()
	claimsSet.claim("https://iadvize.com/userId","c42ab96d-0637-4d1e-8be3-0a872d9d1ef1")
	
	// For security reason, it’s better to set a quick expiration time. As this token will just be used to initialise a new secured visitor session on iAdvize 1 minute seems a good duration.
	claimsSet.expirationTime(ZonedDateTime.now().plusMinutes(1))
	val signer = new RSASSASigner(clientPrivateKey)
	val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet.build())
	
	// Sign the JWT using your private key: it became a JWS.
	signedJWT.sign(signer)
	signedJWT
}

 

For security reason, it’s better to set a quick expiration time. As this token will just be used to initialize a new secured visitor session on iAdvize 1 minute seems a good duration.
createJWE()
Once we have a signed JWT, a JWS, we could encrypt this JWS to finally have a JWE.

 

def createJWE(iadvizePublicKey : PublicKey, jws : SignedJWT) : JWEObject =
{
	// Specify the header(encryption algorithms) and the payload (the JWS generated in the previous step).
	val header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128CBC_HS256).build()
	val payload = new Payload(jws)
	val jwe = new JWEObject(header, payload)
	
	// Encrypt the token using the iAdvize Public key.
	jwe.encrypt(new com.nimbusds.jose.crypto.RSAEncrypter(iadvizePublicKey.asInstanceOf[java.security.interfaces.RSAPublicKey]))
	jwe
}

 

Here is how the createJWS function would be modified to add the firstName and the lastName fields :

def createJWS(clientPrivateKey: PrivateKey) : SignedJWT = {
val claimsSet = new JWTClaimsSet.Builder()
claimsSet.claim("https://iadvize.com/userId","c42ab96d-0637-4d1e-8be3-0a872d9d1ef1")
claimsSet.claim("https://iadvize.com/visitorData", JSONObjectUtils.parse("""{"firstName: "Jane", "lastName": "Doe"}"""))

// For security reason, it’s better to set a quick expiration time. As this token will just be used to initialise a new secured visitor
session on iAdvize 1 minute seems a good duration.
claimsSet.expirationTime(ZonedDateTime.now().plusMinutes(1))
val signer = new RSASSASigner(clientPrivateKey)
val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.RS256), claimsSet.build())

//Sign the JWT using your private key: it became a JWS.
signedJWT.sign(signer)
signedJWT
}

 

In the example above, the visitorData claim is a JSON object containing the “firstName” and “lastName” fields. Here is a complete example with all possible fields :

{
    "country": "France",
    "firstName": "Jane",
    "lastName": "Doe",
    "zipCode": "44000",
    "address": "9 rue Nina Simone",
    "phoneNumber": "+33651229856",
    "city": "Nantes",
    "email": "jane.doe@email.com"
  }

 

Here is how the same function would look in Node.js :

const jwt = require("jsonwebtoken");

function createJWS(clientPrivateKey) {
  return jwt.sign(
    {
      "https://iadvize.com/userId": "test_documentation",
      iss: "https://test.iadvize.com",
      "https://iadvize.com/visitorData": {
        firstName: "Jane",
        lastName: "Doe",
    },
      // For security reasons, it’s better to set a small expiration time. As this token will just be used to initialise a new secured visitor session on iAdvize, 1 minute seems like a good duration.
      exp: Date.now() + 60 * 1000,
    },
    clientPrivateKey
  );
}

 

4. FAQ

4.1 Where could I find the iAdvize public key?

4.2 My visitor is authenticated (a JWT is in the local storage) but I don’t have a padlock 🔒 on the desk of the agent

  • Be sure, when you are in an authenticated space of your website where the visitor authentication is enabled, to remove the usage of the `extId` system: About the External ID usage (extId)

4.3 JWT not valid

  • Ensure you set all the required claims with the right prefixes
{
	"https://iadvize.com/userId":"myuserid",
	"iss":"https://livechat.iadvize.com",
	"exp":1602060589
}
  • Ensure the JWT is signed with the right algorithm
{
	"alg": "RS256"
}
  • Ensure the JWE is encrypted with the right algorithm
{
	"enc": "A256GCM",
	"alg": "RSA-OAEP-256"
}
  • Ensure you use the right private key and the right iAdvize public key. Ensure iAdvize set up your public key in your settings