Jeton de validation
DANGER
Cette fonctionnalité est dépréciée et sera supprimée dans une prochaine version du SDK Seald. L'alternative recommandée est maintenant l'utilisation de JSON Web Tokens.
Les jetons de validation sont utilisés pour rattacher une identité Seald à :
- un identifiant utilisateur noté
userIddans l'application dans lequel le SDK est intégré ; - l'identifiant de votre application
appId(que l'on retrouve dans l'interface d'administration).
Ils sont dérivés à partir de la validationKey qui doit être est récupérable depuis le tableau d'administration.
Il est utilisé lors de la création d'une identité.
Définition d'un userLicenseToken
Ces jetons sont générés hors ligne à partir du userId de l'utilisateur pour qui on génère un jeton, de l'identifiant de votre application appId, ainsi que la validationKey et le validationKeyId.
Spécification
Ce jeton de licence est généré avec un scrypt, et est de la forme ${validationKeyId}:${nonce}:${token}, avec :
nonceun string de 64 caractères hexadécimaux (0-9eta-f) ;tokenvalantscrypt(`${userId}@${appId}-${validationKey}`, nonce);scryptutilisant comme paramètres :N: 16384 ;r: 8 ;p: 1 ;- taille de sortie : 64 octets.
Il est à usage unique, et n'a pas de durée de validité limite.
Choix du userId
Le userId est un string qui caractérise un utilisateur de façon unique pour votre application. Vous pouvez choisir tout identifiant unique. Selon le modèle d'un utilisateur dans votre application, vous pouvez par exemple choisir : le nom d'utilisateur, son adresse email, son ID interne dans votre application, ...
WARNING
Le userId est stocké en clair dans notre base de données. Pour minimiser les données que Seald enregistre sur vos utilisateurs, il est donc déconseillé d'utiliser une adresse email ou toute autre donnée personnelle. La solution optimale est d'utiliser un UUID.
Comment l'intégrer dans mon application
Un jeton de validation doit être généré par le back-end pour un utilisateur avant la création de son identité Seald et après que le userId ait été assigné par l'application.
WARNING
Il est de la responsabilité de votre back-end d'authentifier l'utilisateur pour qui il génère un userLicenseToken.
Usuellement cela intervient :
- en retour du point d'API de validation du compte par le backend de l'application (validation de l'email par exemple) ;
- dans un point d'API dédié authentifié accessible seulement après que le compte ait été validé par l'application.
TIP
Afin d'éviter le rejeu, chaque nonce ne peut être utilisé qu'une seule fois au sein de votre application : si vous créez et utilisez un userLicenseToken avec unnonce donné, vous ne pourrez plus jamais utiliser un userLicenseToken créé avec le même nonce, que ça soit pour le même utilisateur ou pour un autre.
Pour générer des nonces, vous pouvez soit utiliser un générateur aléatoire, soit les incrémenter.
DANGER
Si un autre userLicenseToken est généré pour le userId d'un utilisateur existant, ce jeton pourra être utilisé pour réassigner le userId en question à un nouvel utilisateur : des données chiffrées pour ce userId après cette réassignation seront déchiffrables par le nouvel utilisateur, et pas par l'ancien ; au contraire, les données préexistantes ne seront toujours lisibles que par l'ancien utilisateur, pas par le nouveau.
Attention à ne pas le faire par erreur : le seul cas où faire ceci peut être légitime est si un utilisateur a perdu toute manière d'accéder à son identité Seald, et doit recréer une identité Seald vierge associée au même userId. Dans ce cas, il faudrait également révoquer l'ancienne identité Seald de l'utilisateur en question à l'aide de la dashboardAPI.
Implémentations
Vous pouvez utiliser l'une des implémentations ci-après pour générer un jeton de validation, ou bien les utiliser comme référence pour ré-implémenter cette génération dans d'autres langages. Si besoin, n'hésitez pas à nous contacter pour obtenir une assistance au développement d'une fonction équivalente dans un autre langage.
Node.JS: implémentation de référence
L'implémentation de référence est en Node.JS :
const crypto = require('crypto')
const { promisify } = require('util')
const randomBytes = promisify(crypto.randomBytes)
const scrypt = promisify(crypto.scrypt)
const generateRandomNonce = async () => (await randomBytes(32)).toString('hex') // 32 bytes encoded in hex is 64 chars
const generateUserLicenseToken = async (nonce, userId, appId, validationKey, validationKeyId) => {
const token = (await scrypt(
Buffer.from(`${userId}@${appId}-${validationKey}`, 'utf8'),
Buffer.from(nonce, 'utf8'),
64,
{ N: 16384, r: 8, p: 1 }
)).toString('hex')
return `${validationKeyId}:${nonce}:${token}`
}GoLang
import (
"crypto/rand"
"golang.org/x/crypto/scrypt"
)
func GenerateRandomNonce() (string, error) {
rawNonce := make([]byte, 32)
_, err := rand.Read(rawNonce)
if err != nil { // Note that err == nil only if we read the requested number of bytes
return "", err
}
return hex.EncodeToString(rawNonce), nil // 32 bytes encoded in hex is 64 chars
}
func GenerateUserLicenseToken(nonce string, userId string, appId string, validationKey string, validationKeyId string) (string, error) {
N := 16384
r := 8
p := 1
tokenBytes, err := scrypt.Key(
[]byte(userId+"@"+appId+"-"+validationKey),
[]byte(nonce),
N, r, p, 64,
)
if err != nil {
return "", err
}
token := hex.EncodeToString(tokenBytes)
userLicenseToken := validationKeyId + ":" + nonce + ":" + token
return userLicenseToken, nil
}Python
import hashlib
import secrets
def generate_random_nonce() -> str:
return secrets.token_bytes(32).hex()
def generate_user_license_token(nonce: str, user_id: str, app_id: str, validation_key: str, validation_key_id: str) -> str:
token: str = hashlib.scrypt(
f"{user_id}@{app_id}-{validation_key}".encode(),
salt=nonce.encode(),
n=16384,
r=8,
p=1,
).hex()
return f"{validation_key_id}:{nonce}:{token}"Ruby
require 'securerandom'
require 'openssl'
def generate_random_nonce()
SecureRandom.hex(32)
end
def generate_user_license_token(nonce, user_id, app_id, validation_key, validation_key_id)
token = OpenSSL::KDF.scrypt(
"#{user_id}@#{app_id}-#{validation_key}",
salt: nonce,
N: 2 ** 14, r: 8, p: 1, length: 64
).unpack("H*")[0]
"#{validation_key_id}:#{nonce}:#{token}"
endVecteur de test
Pour tester votre implémentation de génération du userLicenseToken, vous pouvez essayer votre fonction avec les valeurs suivantes :
const nonce = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
const user_id = "test-userid-for-license"
const app_id = "00000000-0000-1000-a000-7ea300000000"
const validation_key = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
const validation_key_id = "00000000-0000-1000-a000-d11c1d000000"La valeur de sortie attendue est alors la suivante :
const expected_token = "00000000-0000-1000-a000-d11c1d000000:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:fde8bc5ce7a42021062a9b4c2412c2f32cb0c058309d6be8ab67672a3ef9c45cadbb0f4babda52abf294b2de69e04ada1780a1473d3dd7516eaac33087a797e1"Sécurité des jetons de licence
La clé validationKey est secrète. Sa compromission peut engendrer deux effets :
- une utilisation usurpée des quotas de licences associés au compte développeur ;
- un écrasement de l'identité publique Seald associée à un utilisateur, ce qui peut engendrer un scénario de type Man-in-the-middle si d'autres précautions ne sont pas prises.
WARNING
Par conséquent, il est impératif qu'en environnement de production la génération des jetons de licence respecte les conditions suivantes :
- génération côté serveur du jeton ;
- génération seulement pour des utilisateurs authentifiés ;
- protection de la
validationKeypar des mesures périmétriques appropriées (au moins ne pas la mettre directement dans le code source de votre application, mais dans une variable d’environnement).