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
anda-f
);token
beingscrypt(`${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:
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
Test vector
In order to test your implementation of the generation of a userLicenseToken
, you can run your function with the following values:
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:
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:
- a usurpation of the license quotas associated with the developer account;
- 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:
- server side generation of the tokens ;
- generation only for authenticated users;
- 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).