Data Lake Authentication Token Profile¶
This page defines the additional __datalake_token returned alongside the
existing __token by supported session-creation responses. It documents the
issuer contract and the requirements for Data Lake verification. It does not
specify a Data Lake business endpoint or define a session-revocation service.
Token pair and transport¶
A successful session-creation response can contain:
{
"__token": "<existing HS256 session JWT>",
"__datalake_token": "<EdDSA token or null>"
}
__token remains the trading-server session token. HTTP returns it in the
existing Authorization response header; the response does not set a session
cookie. Any cookie storage is client-side behavior. The Data Lake client must
receive __datalake_token explicitly and send it using the Data Lake's own
authentication contract. No extra expiry sibling or token-refresh endpoint is
provided. Obtain a new pair through the applicable login/session flow when the
primary session is reissued.
The pair is emitted for AuthUser, AuthManager (including admin sessions),
SSOAuthUser, ExchangeSSOCode, AuthCustomer, AuthCustomerByCode, and
registration flows that automatically create a session
(CustomerRegistration and CustomerRegistrationByAffiliate). A public registration that does not
establish a session returns neither token. Affiliate API credentials do not receive a Data Lake token. AffiliateLogin
uses the common session-token response path but its unsupported principal type
produces __datalake_token: null. WebSocket authentication
continues to accept the primary token only and does not issue token pairs.
The field __datalake_token is a string when signing succeeds and JSON null
when an active v2 lease, signer, or fresh authorization context is unavailable.
A null Data Lake token does not turn an otherwise successful login into an
authentication failure.
JOSE format¶
The Data Lake value is a standard compact JWT with three Base64URL components. The JOSE header is:
{
"alg": "EdDSA",
"typ": "ion-datalake+jwt",
"kid": "<lowercase SHA-256 of the raw 32-byte signing public key>"
}
EdDSA here uses the license's Ed25519 key. The private key remains inside the
license runtime; the token issuer exposes only a signing operation. The kid
identifies the matching public key and is not a public key itself.
Resolve the signing public key¶
A verifier maps lic_uuid to a trusted license-service host from its own
configuration and requests:
GET /v1/license/license/public-key?lic_uuid={lic_uuid}
The public route returns the trading-server license identity Ed25519 public
key used to verify Data Lake tokens. It does not return the license service's
peer-signing key, a private key, or the encrypted license capsule. Connect over
HTTPS to a trusted host configured by the verifier. Do not fetch a URL from JWT
jku, another JWT header, or a caller-provided value.
The response contains lic_uuid, kid, alg, public_key, status, active,
and expires_at. The verifier must check that the returned UID matches the
requested UID, alg is EdDSA, and the SHA-256 hash of the raw 32-byte key
matches the JWT kid. This kid hash differs from the runtime-license
key_fingerprint, which hashes the DER SubjectPublicKeyInfo encoding.
The service also returns the key for inactive licenses so historical tokens can
still be identified or cryptographically checked. active is true only for
trial, active, or development licenses that have not expired according to the
license-service database clock; expired and blocked licenses return false. A
verifier must deny new authorization when active is false. expires_at
describes the license, not the JWT: it does not add an exp claim or an
independent token expiration. The route does not specify a cache duration.
Check active for each new authorization, or use an explicitly bounded cache
policy that accounts for license-status changes. This lookup does not provide
session revocation or invalidate tokens that were already issued.
Payload¶
Every issued token has these top-level claims:
| Claim | Type | Meaning |
|---|---|---|
ver |
integer | Data Lake token profile version; currently 1. |
iss |
string | ion-server:{lic_uuid} for the issuing licensed trading server. |
aud |
string | Always datalake. |
lic_uuid |
string | License UUID that identifies the signing authority. |
subject_type |
string | Principal kind: account, manager, dealer, admin, customer, crm_manager, or crm_admin. |
sub |
string | Stable principal string {subject_type}:{id}. For an account, {id} is the trading login. |
type |
integer | Original session type code. Mapping: 0 account, 1 manager, 2 dealer, 3 admin, 9 customer, 10 CRM manager, 11 CRM admin. |
id |
integer | Original identity ID, when present in the primary session identity. |
login |
integer | Trading login, when present in the primary session identity. |
customer_id |
integer | Customer ID, when present in the primary session identity. |
impersonated_by |
integer | Manager ID, when the customer session is an impersonation session. |
session_sha256 |
string | Lowercase hex SHA-256 of the exact primary __token bytes. Binds the pair; it is not a revocation lookup by itself. |
iat |
integer | Issued-at Unix timestamp. For ordinary sessions it follows the timestamp used by the primary session identity; for impersonation it is the issue time. |
exp |
integer | Present only for impersonation sessions; equals the enforced expiry of the primary impersonation token. |
authorization |
object | Freshly resolved identity scope described below. |
The identity claims id, login, customer_id, and impersonated_by are
included only when the corresponding field exists in the session identity.
The issuer does not copy arbitrary primary-token claims into the Data Lake JWT.
Ordinary sessions have no exp in this profile because their existing primary
session identity does not have an enforced expiration. Do not infer expiration
from iat.
Authorization object¶
The token contains one authorization shape according to its principal:
Account (subject_type: "account")
| Claim | Type | Meaning |
|---|---|---|
brand |
string | Account's resolved brand. |
group |
string | Account's current trading group. |
login |
integer | Account login. |
Customer (subject_type: "customer")
| Claim | Type | Meaning |
|---|---|---|
brand |
string | Customer's resolved brand. |
customer_id |
integer | Customer ID. |
Staff (manager, dealer, admin, crm_manager, crm_admin)
| Claim | Type | Meaning |
|---|---|---|
brand |
string | Manager's assigned brand. |
groups |
string | Current accessible groups selector. |
desks |
string | Current accessible CRM desks selector. |
admin |
integer | Current admin flag. |
super_admin |
integer | Current cross-brand admin flag. |
permissions |
object | Integer permission flags listed below. |
The permissions object contains access_backoffice, access_crm,
see_accounts, see_accounts_balance, see_trades, reports,
see_customers, see_all_customers, see_customer_contacts, see_clients,
see_leads, see_desks_clients, see_desks_leads, see_clients_contacts,
and see_leads_contacts.
The issuer resolves these resource and permission values from current server managers when it creates the token. Account and customer issuance also requires the entity to remain enabled and its brand to pass the current API admission check.
Decoded example¶
The example shows decoded header and payload values for an impersonated customer. In the compact token, the header and payload are Base64URL encoded and the encoded input is signed; the payload below is illustrative and signature bytes are not shown.
{
"header": {
"alg": "EdDSA",
"typ": "ion-datalake+jwt",
"kid": "<64 lowercase hexadecimal characters>"
},
"payload": {
"ver": 1,
"iss": "ion-server:11111111-2222-4333-8444-555555555555",
"aud": "datalake",
"lic_uuid": "11111111-2222-4333-8444-555555555555",
"subject_type": "customer",
"sub": "customer:1042",
"type": 9,
"id": 1042,
"customer_id": 1042,
"impersonated_by": 1001,
"session_sha256": "<64 lowercase hexadecimal characters>",
"iat": 1792430400,
"exp": 1792434000,
"authorization": {
"brand": "ALFA",
"customer_id": 1042
}
}
}
For an ordinary account/manager/customer session the exp field is omitted.
Verification requirements¶
A Data Lake verifier must:
- Require exactly three compact-JWT components and pin
algtoEdDSAandtyptoion-datalake+jwt. - Resolve
lic_uuidthrough the trusted license-service host configured by the verifier using the public-key route above. Require the returned UID to match, compare the raw-keykid, and requireactive: truefor new authorization. Do not use the license-service peer/public key embedded in the license capsule; that key serves the opposite verification direction. - Verify the Ed25519 signature before trusting any claim, then require
aud = datalake,iss = ion-server:{lic_uuid}, and a supportedverandsubject_type/typepair. - Apply the
authorizationbrand, group, desk, and permission scope to every Data Lake request. A valid signature attests the licensed server's issued claims; it does not authorize every tenant or resource automatically. - Enforce
expwhen present. Ordinary token expiry follows the primary session's existing behavior and is not represented by anexpclaim here. - Compare
session_sha256with the primary token only if the verifier receives that token through a trusted integration. The digest binds the two values but cannot revoke a copied Data Lake token by itself.
A shared session-state or revocation integration is required if logout, disablement, or reissue must immediately invalidate a previously issued Data Lake token. Such an integration is not defined by this token format and is not documented as implemented here. Until then, a signature-valid ordinary token remains valid according to the Data Lake verifier's own acceptance policy.
Main Server REST gateway integration¶
The REST gateway supports an opt-in authentication profile for individual
service routes. A route enables it with auth: "datalake" and must
declare a non-empty licenseAccess.subjects allowlist. The supported subject
names are account, manager, dealer, admin, customer, crm_manager,
and crm_admin. This allowlist is required even when a route intends to permit
all subject types.
In a versioned Moleculer service, add route metadata to each action. For
example, a customer route may allow customer tokens, while a staff reporting
route may require the reports permission:
actions: {
customerSummary: {
route: {
path: "customer/summary",
method: "GET",
auth: "datalake",
licenseAccess: { subjects: ["customer"] }
}
},
staffReport: {
route: {
path: "staff/report",
method: "GET",
auth: "datalake",
licenseAccess: {
subjects: ["manager", "admin"],
permissions: ["reports"]
}
}
}
}
licenseAccess.permissions is optional. When supplied, it must be an array of
known permission names and every listed permission must equal 1 in the signed
token's staff authorization scope. Customer and account token profiles do not
contain staff permissions, so a route requiring a permission should allow only
staff subject types. The recognized names are access_backoffice,
access_crm, see_accounts, see_accounts_balance, see_trades, reports,
see_customers, see_all_customers, see_customer_contacts, see_clients,
see_leads, see_desks_clients, see_desks_leads, see_clients_contacts,
and see_leads_contacts. The gateway checks these route-level conditions; it
does not apply generic row filters or automatically authorize access to every
resource in the claims. The downstream action must enforce the relevant
brand, group, desks, login, customer_id, and permission scope for the
requested resource.
The client sends the compact Data Lake JWT as the raw x-data-token header
value, without a Bearer prefix. The gateway does not use Authorization as
a fallback for this profile; legacy RS256 routes retain their existing
Authorization handling. CORS allows the x-data-token request header:
GET /v1/data/customer/summary?customer_id=1042 HTTP/1.1
Host: api.example.test
x-data-token: <compact EdDSA Data Lake JWT>
For an authenticated request, the gateway passes request data and trusted
authentication context to the configured service action. For example, the
forwarded data includes the requested customer_id, request metadata such as
__ip, and gateway-created fields similar to:
{
"customer_id": 1042,
"__ip": "203.0.113.10",
"__auth_profile": "datalake",
"__token": "<the raw compact Data Lake JWT>",
"__access": {
"ver": 1,
"iss": "ion-server:11111111-2222-4333-8444-555555555555",
"aud": "datalake",
"lic_uuid": "11111111-2222-4333-8444-555555555555",
"subject_type": "customer",
"sub": "customer:1042",
"type": 9,
"id": 1042,
"customer_id": 1042,
"session_sha256": "<64 lowercase hexadecimal characters>",
"iat": 1792430400,
"authorization": {
"brand": "ALFA",
"customer_id": 1042
}
}
}
The gateway removes caller-supplied __* fields before attaching its trusted
context. It does not translate this profile into legacy roles, elevate admin
flags, or create an RS256 session token. __access is internal forwarded
context and is removed from the HTTP response. When gateway response caching is
enabled for such a route, its cache namespace includes the license UID, subject,
and hash of the presented token in addition to the route's configured cache
keys.
The verifier resolves the public key through the configured broker action
(license.license.public_key by default). Its additive gateway settings are:
| Setting | Default | Meaning |
|---|---|---|
datalake.licenseAction |
license.license.public_key |
Broker action used to look up the trusted license key and state. |
datalake.timeoutMs |
3000 |
Lookup timeout in milliseconds. |
datalake.cacheTtlMs |
0 |
Public-key and active-state cache duration in milliseconds; zero disables the verifier cache. A positive TTL can delay recognition of status changes by up to that TTL; token and license expiration are still checked against the current verifier clock. |
datalake.maxCacheEntries |
256 |
Maximum verifier cache/in-flight lookup entries. |
Verification failures use these HTTP error types: 401 INVALID_DATALAKE_TOKEN
for malformed tokens, wrong profile, key ID mismatch, or invalid signature;
403 DATALAKE_ACCESS_DENIED for route policy mismatch or an inactive/expired
license or impersonation token; and 503 LICENSE_KEY_UNAVAILABLE when the
trusted license lookup cannot provide a usable key. The existing gateway error
envelope includes code, type, message, and data; for example:
{
"code": 401,
"type": "INVALID_DATALAKE_TOKEN",
"message": "Invalid datalake token",
"data": {}
}
This profile is opt-in and does not alter legacy RS256 authentication. It does
not harden /api/sessions, /api/session, upload routes, or middleware order.
The gateway repository currently has no Data Lake business routes enabled by
this profile, so downstream services must opt in route by route and implement
resource-level authorization. This mechanism does not add logout or session
revocation: session_sha256 binds the Data Lake token to the corresponding
primary token only when a trusted verifier can compare both values.
Issuance failures and compatibility¶
The server returns the existing primary session token when normal authentication
succeeds. Data Lake token issuance requires an active v2 license lease, the
available signing callback, a supported identity, and successful fresh scope
resolution. If any requirement fails, __datalake_token is null; it does not
change the existing login status. Legacy v1 runtime and older ABI 10 libraries
without the optional signing API therefore continue issuing ordinary sessions
without this field's signed value.
STCore SDK compatibility¶
STCore exposes the optional additive C ABI function:
int stcore_license_gate_issue_datalake_token(
StLicenseGateHandle* handle,
const char* claims,
size_t claims_size,
StLicenseTokenWriter writer,
void* context) noexcept;
typedef void (*StLicenseTokenWriter)(
const char* token,
size_t token_size,
void* context) noexcept;
New hosts discover this symbol dynamically (with dlsym) and may use it when
present. The capability is additive: ABI version 10 and existing structure
layouts remain unchanged, and the signing private key is never exported from
STCore. The host must enforce the active v2 license lease before requesting a
token; the signing function itself does not verify Data Lake requests or
replace the Data Lake verifier.
The claims input is a non-empty JSON byte sequence of at most 65,536 bytes.
It has this internal input shape (the identity object is an input to the
signer, not a nested claim in the resulting wire JWT):
{
"identity": { "type": 0, "login": 100001, "exp": 1792430400 },
"session_sha256": "<64 lowercase hexadecimal characters>",
"authorization": { "brand": "ALFA", "group": "Standard", "login": 100001 }
}
The callback runs synchronously. Its token pointer refers to STCore-owned
memory and is valid only until the callback returns; copy the bytes inside the
callback if they must be retained. Return status 0 means a token was issued,
-2 means the capability is unsupported by the runtime (including v1/no v2
runtime), and -1 means invalid arguments, invalid claims, or signing failure.
The function performs token issuance only; Data Lake verification,
authorization enforcement, and session revocation remain the responsibility of
the integrating services.