Skip to content

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:

  1. Require exactly three compact-JWT components and pin alg to EdDSA and typ to ion-datalake+jwt.
  2. Resolve lic_uuid through the trusted license-service host configured by the verifier using the public-key route above. Require the returned UID to match, compare the raw-key kid, and require active: true for new authorization. Do not use the license-service peer/public key embedded in the license capsule; that key serves the opposite verification direction.
  3. Verify the Ed25519 signature before trusting any claim, then require aud = datalake, iss = ion-server:{lic_uuid}, and a supported ver and subject_type/type pair.
  4. Apply the authorization brand, 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.
  5. Enforce exp when present. Ordinary token expiry follows the primary session's existing behavior and is not represented by an exp claim here.
  6. Compare session_sha256 with 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.