Skip to content

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 et a-f) ;
  • token valant scrypt(`${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 :

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

go
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

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

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 :

go
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 :

go
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 :

  1. une utilisation usurpée des quotas de licences associés au compte développeur ;
  2. 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 :

  1. génération côté serveur du jeton ;
  2. génération seulement pour des utilisateurs authentifiés ;
  3. 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).