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.

 

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 de https://iadvize.com/userId est indispensable ⚠️

 

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
}

 

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.