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é
userId
dans 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 :
nonce
un string de 64 caractères hexadécimaux (0-9
eta-f
) ;token
valantscrypt(`${userId}@${appId}-${validationKey}`, nonce)
;scrypt
utilisant 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}"
end
Vecteur 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
validationKey
par 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).