Skip to content

Validation token

DANGER

This feature is deprecated and will be removed in a future version of the Seald SDK. The recommended alternative is using JSON Web Tokens instead.

Validation tokens are used to attach a Seald identity to :

  • a user identifier noted userId in the application in which the SDK is integrated ;
  • your application identifier appId (found in the administration dashboard).

They are derived from the validationKey which can be retrieved from the administration dashboard.

It is used during identity creation.

Definition of a userLicenseToken

These tokens are generated offline from the userId of the user for whom a token is being generated, the appId of your application, as well as the validationKey and the validationKeyId.

Specification

This license token is generated with a scrypt, and is of the form ${validationKeyId}:${nonce}:${token}, with:

  • nonce a string of 64 hexadecimal characters (0-9 and a-f);
  • token being scrypt(`${userId}@${appId}-${validationKey}`, nonce);
  • scrypt using as parameters:
    • N: 16384;
    • r: 8;
    • p: 1;
    • output size: 64 bytes.

It is single-use, and there is no time limit on the validity of the token.

Choosing the userId

The userId is a string that specifies a user uniquely for your application. You can choose any unique identifier. Depending on the user model in your application, you can for example choose: the username, their email address, their internal ID in your application, ...

WARNING

The userId is stored in clear text in our database. In order to minimize the data that Seald stores about your users, it is not recommended using an email address or any other personal identifying information. The optimal is to use a UUID.

How to integrate it within my application

A validation token must be generated by the back-end for a user before the Seald identity is created and after the userId has been assigned by the application.

WARNING

It is the responsibility of your back-end to authenticate the user for whom it is generating a userLicenseToken.

Usually this happens:

  • in return of the API endpoint for account validation by the application backend (email validation for example);
  • in a dedicated authenticated API endpoint accessible only after the account has been validated by the application.

TIP

In order to avoid replay, each nonce is single-use for your whole application: if you create and use a userLicenseToken with a given nonce, you will never be able to use another userLicenseToken created with the same nonce, whether it is for the same user or for another.

To generate nonces, you can either use a random generator, or increment them.

DANGER

If another userLicenseToken is generated for the userId of an existing user, this token can be used to reassign the userId in question to a new user: encrypted data for this userId after this reassignment will be decryptable by the new user, and not by the old one; on the contrary, pre-existing data will still be readable only by the old user, not by the new one.

Be careful not to do this by mistake: the only case where doing this can be legitimate is if a user has lost any way to access their Seald identity, and has to re-create a new Seald identity associated with the same userId. In this case, you should also revoke the old Seald identity of the user in question using the dashboardAPI.

Implementations

You can use one of the following implementations to generate a validation token, or use them as a reference to re-implement this generation in other languages. If needed, do not hesitate to contact us for assistance in developing an equivalent function in another language.

Node.JS: reference implementation

The reference implementation is in 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}`
}
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
}
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}"
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
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

Test vector

In order to test your implementation of the generation of a userLicenseToken, you can run your function with the following values:

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

The expected output value is the following:

go
const expected_token = "00000000-0000-1000-a000-d11c1d000000:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:fde8bc5ce7a42021062a9b4c2412c2f32cb0c058309d6be8ab67672a3ef9c45cadbb0f4babda52abf294b2de69e04ada1780a1473d3dd7516eaac33087a797e1"
const expected_token = "00000000-0000-1000-a000-d11c1d000000:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef:fde8bc5ce7a42021062a9b4c2412c2f32cb0c058309d6be8ab67672a3ef9c45cadbb0f4babda52abf294b2de69e04ada1780a1473d3dd7516eaac33087a797e1"

Security of the validation tokens

The validationKey is secret. If it were leaked, it could result in two effects:

  1. a usurpation of the license quotas associated with the developer account;
  2. overwriting the Seald public identity associated with a user, which can lead to a Man-in-the-middle attack if other precautions are not taken.

WARNING

Therefore, it is imperative that in production environment the generation of the license tokens respects the following conditions:

  1. server side generation of the tokens ;
  2. generation only for authenticated users;
  3. protection of the validationKey by appropriate perimeter security measures (at least do not put it directly in the source code of your application, but in an environment variable).