Messagerie authentifiée iAdvize - implémentation du backend web

Le processus d'authentification du visiteur consiste en la création d'un Token (ou jeton) signé et crypté qui est créé depuis votre backend et transmis au tag iAdvize via votre frontend (site web) afin qu'iAdvize puisse reconnaître le visiteur et afficher les données relatives au visiteur et à la conversation en conséquence.

 

1. Informations importantes et recommandations


1.1 Identifiant de l'utilisateur

 

Il doit être unique par utilisateur - l'ID utilisateur ne peut pas être recyclé d'un utilisateur à l'autre.

Il doit comporter 255 caractères maximum.

Si vous ne respectez pas ces directives, iAdvize considérera tous les visiteurs comme un seul et même visiteur. Nous associerons alors toutes les conversations des visiteurs au même identifiant utilisateur. Cela crée un problème de confidentialité : les visiteurs auront alors accès au contenu des conversations de chacun, y compris le texte et les pièces jointes.

 

1.2 Cryptage des Tokens (ou jetons)

 

Lorsque vous générez un JWE qui contient votre identifiant utilisateur, votre bibliothèque pour générer ce jeton doit prendre en charge A256GCM et RSA_OAEP_256 pour créer le JWE. Le JWS interne doit être signé avec RS256.

 

1.3 Stockage des clés privées

 

Nous stockons notre clé privée à l'aide d'un outil de sécurité externe, de sorte que notre clé privée n'est pas exposée à travers notre code ou tout accès à la base de données.

 

1.4 À propos de l'usage de l'identifiant Externe (extID)

 

Le système d'authentification des visiteurs remplace entièrement l'utilisation de l'"ExtID". Ainsi, si vous utilisez le système d'authentification des visiteurs dans un espace authentifié de votre site web, vous devez vous assurer que vous n'utilisez pas le système "ExtID" en parallèle.

 

1.5 Envoi de données sur les visiteurs dans le jeton JWT

En plus de la déclaration de l'ID de l'utilisateur, une déclaration facultative de données du visiteur peut être ajoutée au jeton JWT. Voici à quoi il ressemblerait, avant le cryptage :

 

 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


 

 

Il peut contenir les champs suivants, tous sont des chaînes de caractères optionnelles :

address
city
country
email
firstName
lastName
phoneNumber
zipCode

 

Ces champs seront ensuite affichés sur le pupitre du conseiller :

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

 

Voir la section 3. pour la mise en œuvre du backend technique.

2. Processus détaillé de signature et de chiffrement


2.1 Génération et cryptage de paires de clés

 

lD2vXG5Y.png

 

Vous devrez générer une paire de clés : une clé publique/ une clé privée. Vous devrez partager votre clé publique avec iAdvize.
iAdvize vous fournit sa clé publique.

Une façon de générer vos clés (vous pouvez adapter le nombre de bits) :

 

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

 

2.2 Signature et chiffrement

 

YfLN9KHI.png

 

iAdvize effectuera le processus inverse qui consiste à décrypter le JWE (à l'aide de la clé privée d'iAdvize) et à vérifier la signature du token (à l'aide de la clé publique du Client) pour finalement extraire l'identifiant de l'utilisateur et créer une Session Authentifiée Visiteur (un nouveau JWS généré par iAdvize en interne) :

 

pnLWCZHY.png

 


2.4 Clé publique d'iAdvize (à utiliser pour la production)

 

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

 

3. Implémentation technique Backend

 

À noter : vous devrez définir une "claim" personnalisée qui sera intégrée dans le jeton signé (JWS). Vous devez préfixer toutes les "claims" avec https://iadvize.com/.

 

⚠️ La claim https://iadvize.com/visitorData est facultative ⚠️

 

3.1 La librairie JWT

 

Tout d'abord, pour utiliser JWT, vous devez choisir la bonne bibliothèque qui correspond à la langue de votre backend.
Vous pouvez trouver sur le site officiel de JWT une liste de nombreuses bibliothèques implémentées pour de nombreux langages.
La bibliothèque choisie doit supporter "A256GCM" et "RSA_OAEP_256" pour créer le JWE, le JWS interne doit être signé avec "RS256".

 

3.2 Un exemple en SCALA

 

Vous avez ici une mise en œuvre technique de la solution en 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
	}
}

 

Les éléments clés pour vous sont les fonctions "createJWS()" et "createJWE()" que nous allons détailler ci-dessous.

 

"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
}

 

Pour des raisons de sécurité, il est préférable de fixer un délai d'expiration rapide. Comme ce jeton ne sera utilisé que pour initialiser une nouvelle session sécurisée de visiteur sur iAdvize, 1 minute semble une bonne durée.


createJWE() :


Une fois que nous avons un JWT signé, un JWS, nous pouvons crypter ce JWS pour finalement avoir un 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
}

 

Voici comment la fonction createJWS serait modifiée pour ajouter les champs firstName et lastName :

 

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
}

 

Dans l'exemple ci-dessus, la réclamation visitorData est un objet JSON contenant les champs "firstName" et "lastName". Voici un exemple complet avec tous les champs possibles :

 

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

 

Voici à quoi ressemblerait la même fonction en 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. Où puis-je trouver la clé publique d'iAdvize ?

4.2. Mon visiteur est authentifié (un JWT est dans le stockage local) mais je n'ai pas de cadenas 🔒 sur le bureau de l'agent.

4.3. JWT n'est pas valide

  • Assurez-vous de définir toutes les allégations requises avec les bons préfixes.
{
	"https://iadvize.com/userId":"myuserid",
	"iss":"https://livechat.iadvize.com",
	"exp":1602060589
}
  • Assurez-vous que le JWT est signé avec le bon algorithme.
{
	"alg": "RS256"
}
  • Assurez-vous que le JWE est crypté avec le bon algorithme.
{
	"enc": "A256GCM",
	"alg": "RSA-OAEP-256"
}
  • Assurez-vous que vous utilisez la bonne clé privée et la bonne clé publique d'iAdvize. Assurez-vous qu'iAdvize a configuré votre clé publique dans vos paramètres.