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 |
{ "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
firstName
lastName
phoneNumber
zipCode
These fields will then be displayed on the desk of the agent :
See the 3. Technical Backend Implementation section.
2. Signature and Encryption Detailed Process
2.1 Key pair generation and sharing
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
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):
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?
- Please see 2.4 iAdvize Public key (use for Production)
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