This guide explains the RODiT TokenMetadata
structure and how to populate it for three profiles: root, server, and client. It also clarifies defaulting, FrontEnd/BackEnd responsibilities, and constraints.
TokenMetadata structure (as implemented)
All fields are strings on-chain for robust, uniform serialization:
-
openapijson_url: String
— URL to the OpenAPI (or equivalent) JSON describing the signing/API service. -
not_after: String
— Expiration date inYYYY-MM-DD
(use1970-01-01
to indicate no limit). -
not_before: String
— Activation date inYYYY-MM-DD
. -
max_requests: String
— Integer as string (0 = unlimited). -
maxrq_window: String
— Integer as string representing the rate-window seconds (0 = unlimited window). -
webhook_url: String
— HTTPS endpoint to receive event callbacks. -
webhook_cidr: String
— CIDR(s) allowed to deliver webhooks. -
userselected_dn: String
— Optional user-specified display name / DN. -
allowed_cidr: String
— CIDR(s) allowed for runtime usage (environment-bound). -
allowed_iso3166list: String
— JSON string, e.g.{"allow":["WLD"]}
indicating country policy. -
jwt_duration: String
— Integer as string; JWT validity seconds (0 = unlimited). -
permissioned_routes: String
— JSON string describing entity/method permissions. -
subjectuniqueidentifier_url: String
— URL pointing to a stable identity/subject descriptor for the token’s subject. -
serviceprovider_id: String
— An internal identifier string for the issuing service. -
serviceprovider_signature: String
— Issuer’s signature over fee/issuance data (details covered in a separate article).
Notes:
- Although numeric or JSON-like semantically, the contract stores all fields as strings. When fields carry JSON (e.g.,
allowed_iso3166list
,permissioned_routes
), pass a serialized JSON string. - Date format is canonical
YYYY-MM-DD
. Use UTC convention.
Profiles and recommended defaults
1) Root RODiT (mint root)
- Purpose: Top-level authority for a service provider; may be used as input for minting a server token.
Field | Responsibility | Recommended value / rule |
---|---|---|
token_id |
FrontEnd, ulid()
|
Unique stable ID at issuance time. |
openapijson_url |
FrontEnd | URL of the signing API /api-docs . |
not_after |
FrontEnd |
1970-01-01 (no limit). |
not_before |
FrontEnd | Today (UTC). |
max_requests |
API Configuration |
0 (no limit). |
maxrq_window |
API Configuration |
0 (no limit). |
webhook_url |
API Configuration |
https://not-set.example.com/webhook . |
webhook_cidr |
API Configuration |
0.0.0.0/0 . |
userselected_dn |
API Configuration | Empty string "" . |
allowed_cidr |
FrontEnd | Where mint server can run, e.g. 0.0.0.0/0 for unconstrained lab testing. |
allowed_iso3166list |
API Configuration |
{"allow":["WLD"]} . |
jwt_duration |
API Configuration |
0 (no limit). |
permissioned_routes |
API Configuration |
{"entities":{"name":"thing","methods":{"/crud/onemethod":"+0","/crud/anothermethod":"+0"}}} . |
subjectuniqueidentifier_url |
FrontEnd | URL of signing API for mint server. |
serviceprovider_id |
FrontEnd |
bc=near.org;sc=20251001-rodit-org.testnet;id=<token_id> . |
serviceprovider_signature |
FrontEnd | Calculated by issuer; scheme covered separately. |
2) Server RODiT (mint server)
- Purpose: Server-side token, that is also used to authorize issuance/operations for client tokens.
- Derived from root; may use root as an input.
Field | Responsibility | Recommended value / rule |
---|---|---|
token_id |
BackEnd, ulid()
|
Unique ID generated by backend. |
openapijson_url |
BackEnd | URL of signing API /api-docs . |
not_after |
FrontEnd |
1970-01-01 (no limit). |
not_before |
BackEnd | Same as root’s not_before . |
max_requests |
API Configuration |
0 (no limit). |
maxrq_window |
API Configuration |
0 (no limit). |
webhook_url |
API Configuration |
https://not-set.example.com/webhook . |
webhook_cidr |
FrontEnd |
0.0.0.0/0 . |
userselected_dn |
API Configuration | Empty string "" . |
allowed_cidr |
FrontEnd | Where mint client can run, e.g. 0.0.0.0/0 . |
allowed_iso3166list |
FrontEnd |
{"allow":["WLD"]} . |
jwt_duration |
FrontEnd |
3600 . |
permissioned_routes |
FrontEnd | Same JSON pattern as root. Could be BackEnd in some implementations. |
subjectuniqueidentifier_url |
FrontEnd | URL (stable identifier for this server token’s subject). |
serviceprovider_id |
BackEnd | Use serviceprovider_id of root. |
serviceprovider_signature |
BackEnd | Calculated by backend (root’s private context / Secret Manager). |
3) Client RODiT (mint client)
- Purpose: End-client/API-consumer token tied to actual API routing and limits.
Field | Responsibility | Recommended value / rule |
---|---|---|
token_id |
BackEnd, ulid()
|
Unique ID generated by backend. |
openapijson_url |
BackEnd | URL /api-docs of the public API. |
not_after |
FrontEnd |
1970-01-01 (no limit) – has a cost if extended. |
not_before |
BackEnd | Same as server’s not_before . |
max_requests |
FrontEnd | Numeric as string – has a cost. |
maxrq_window |
FrontEnd | Numeric as string – has a cost. |
webhook_url |
FrontEnd | Defaults to https://not-set.example.com/webhook if unused. |
webhook_cidr |
BackEnd | Same as server RODiT (operational delivery network). |
userselected_dn |
FrontEnd | Optional label. |
allowed_cidr |
BackEnd | Where the API will be served from (network boundary). |
allowed_iso3166list |
BackEnd |
{"allow":["WLD"]} unless geo-restricted. |
jwt_duration |
BackEnd | Policy-driven (e.g., 3600 ). |
permissioned_routes |
FrontEnd | Route-level permissions JSON string. |
subjectuniqueidentifier_url |
BackEnd | URL; stable subject identity (client/service). |
serviceprovider_id |
BackEnd | Root token’s serviceprovider_id (or derived). |
serviceprovider_signature |
BackEnd | Calculated by backend (Secret Manager). |
Cost flags (business policy):
- Client
not_after
,max_requests
, andmaxrq_window
may incur costs; encode as needed in fee JSON/signature (covered separately).
JSON examples
All examples use serialized JSON strings for JSON-carrying fields.
Root example
{
"openapijson_url": "https://root.internal.example/api-docs",
"not_after": "1970-01-01",
"not_before": "2025-08-29",
"max_requests": "0",
"maxrq_window": "0",
"webhook_url": "https://not-set.example.com/webhook",
"webhook_cidr": "0.0.0.0/0",
"userselected_dn": "",
"allowed_cidr": "0.0.0.0/0",
"allowed_iso3166list": "{\"allow\":[\"WLD\"]}",
"jwt_duration": "0",
"permissioned_routes": "{\"entities\":{\"name\":\"thing\",\"methods\":{\"/crud/onemethod\":\"+0\",\"/crud/anothermethod\":\"+0\"}}}",
"subjectuniqueidentifier_url": "https://root.internal.example/subject",
"serviceprovider_id": "bc=near.org;sc=20251001-rodit-org.testnet;id=<ROOT_TOKEN_ID>",
"serviceprovider_signature": "<signature>"
}
Server example
{
"openapijson_url": "https://server.internal.example/api-docs",
"not_after": "1970-01-01",
"not_before": "2025-08-29",
"max_requests": "0",
"maxrq_window": "0",
"webhook_url": "https://not-set.example.com/webhook",
"webhook_cidr": "0.0.0.0/0",
"userselected_dn": "",
"allowed_cidr": "0.0.0.0/0",
"allowed_iso3166list": "{\"allow\":[\"WLD\"]}",
"jwt_duration": "3600",
"permissioned_routes": "{\"entities\":{\"name\":\"thing\",\"methods\":{\"/crud/onemethod\":\"+0\",\"/crud/anothermethod\":\"+0\"}}}",
"subjectuniqueidentifier_url": "https://server.internal.example/subject",
"serviceprovider_id": "bc=near.org;sc=20251001-rodit-org.testnet;id=<ROOT_TOKEN_ID>",
"serviceprovider_signature": "<signature>"
}
Client example
{
"openapijson_url": "https://api.public.example/api-docs",
"not_after": "1970-01-01",
"not_before": "2025-08-29",
"max_requests": "100000",
"maxrq_window": "86400",
"webhook_url": "https://not-set.example.com/webhook",
"webhook_cidr": "203.0.113.0/24",
"userselected_dn": "example-client",
"allowed_cidr": "203.0.113.0/24",
"allowed_iso3166list": "{\"allow\":[\"WLD\"]}",
"jwt_duration": "3600",
"permissioned_routes": "{\"entities\":{\"name\":\"thing\",\"methods\":{\"/crud/onemethod\":\"+0\",\"/crud/anothermethod\":\"+0\"}}}",
"subjectuniqueidentifier_url": "https://subjects.example/clients/123",
"serviceprovider_id": "bc=near.org;sc=20251001-rodit-org.testnet;id=<ROOT_OR_SERVER_TOKEN_ID>",
"serviceprovider_signature": "<signature>"
}
Field-by-field guidance and validation tips
-
Dates: use
YYYY-MM-DD
.1970-01-01
is treated as “no limit” conventionally in this design. -
Numeric strings:
max_requests
,maxrq_window
,jwt_duration
are strings but should parse to non-negative integers.0
means unlimited for the first two; forjwt_duration
,0
means unlimited validity. -
JSON-carrying strings: ensure the string is valid JSON when parsed off-chain (e.g., UI and backend). Example patterns:
-
allowed_iso3166list
:{"allow":["WLD"]}
-
permissioned_routes
:{"entities":{"name":"thing","methods":{"/crud/onemethod":"+0","/crud/anothermethod":"+0"}}}
-
- CIDRs: Accept lists or a single CIDR; if multiple, standardize as comma-separated or a JSON list encoded as a string (match your FrontEnd/BackEnd convention consistently).
-
serviceprovider_id
: Recommended format:bc=near.org;sc=<contract_account>;id=<token_id>
. Example:bc=near.org;sc=20251001-rodit-org.testnet;id=01J8...ULID
.End-to-end flow sketch
- Root minted (private server). Establishes base policy and issuer identity.
- Server minted referencing root (private network). Tightens runtime constraints (e.g., JWT, allowed CIDR) and sets operational identity.
- Client minted referencing server (public Internet). Sets actual API routes, rate limits, and serving CIDR. Client-side limits may incur cost and should be reflected in the minting fee JSON.
Operational layers and responsibilities
This section maps deployment layers to responsibilities, how they relate to Root/Server/Client profiles, where they authenticate, what they sign or mint, and their canonical endpoints.
-
Portal Sanctum FE
- Relates to: Root profile inputs (operator UI) and Portal configuration.
- User metadata: FrontEnd-driven via .env and config.
- Authenticates to: Signroot.
- Signs: Portal (configuration payloads); feeds Sanctum and Portal flows.
- Mints: Portal.
- URL: https://testnet-serverfe.cableguard.net:6443/
- Notes: Provides operator inputs used to construct Root RODiT TokenMetadata (e.g., openapijson_url, allowed_cidr).
-
Signroot
- Relates to: Root RODiT ("mint root").
- System metadata: config (trusted settings).
- Authenticates to: Portal Sanctum FrontEnd (receives FrontEnd auth/session context).
- Signs: Sanctum (root-level policy/signature) and produces issuer signatures.
- Mints: Nothing directly exposed beyond root issuance service; used as input to Server minting.
- URL: https://dev-api.cableguard.net:8443
- Notes: Produces serviceprovider_id and serviceprovider_signature for the Root token.
-
Server FE
- Relates to: Server RODiT ("mint server").
- User metadata: FrontEnd.
- Authenticates to: Sanctum.
- Signs: Uses Signserver together with Portal context when needed.
- Mints: Server.
- URL: https://testnet-serverfe.cableguard.net:2443/
- Notes: Supplies FrontEnd values like jwt_duration, allowed_cidr, potentially permissioned_routes.
-
Signserver
- Relates to: Server RODiT issuance (backend signer).
- System metadata: config.
- Authenticates to: Sanctum (trusted channel).
- Signs: Server FrontEnd with Sanctum context.
- Mints: Server (executes the mint call) or assists mint pipeline.
- URL: https://dev-api.cableguard.net:8443
- Notes: Ensures Server token inherits/aligns with Root (e.g., not_before, serviceprovider_id).
-
Client FE
- Relates to: Client RODiT ("mint client").
- User metadata: FrontEnd.
- Authenticates to: Server.
- Signs: Nothing; uses Signclient.
- Mints: Client.
- URL: mint.APIURL:4443
- Notes: Chooses rate limits and dates that may incur costs (max_requests, maxrq_window, not_after).
-
Signclient
- Relates to: Client RODiT issuance (backend signer).
- System task: Sanitize permissioned_routes against server policy.
- System metadata: config.
- Authenticates to: Server.
- Signs: Signportal with Server (client issuance bundles).
- Mints: Nothing (prepares signed payloads for mint).
- URL: sign.APIURL:8443
- Notes: Enforces that permissioned_routes in the client metadata are a subset of allowed routes from the Server token/profile. Rejects unknown/unsafe routes.
-
Signportal
- Relates to: Client onboarding via Portal.
- System metadata: config.
- Authenticates to: Portal.
- Signs: Signclient Portal (client-side flows coordinated with portal session).
- Mints: Client.
- URL: https://testnet-clientfe.cableguard.net:6443/
- Notes: Fronts the client mint UX; coordinates with Signclient and Server for policy validation.
Layer-to-Token profile mapping
- Root token: produced by Signroot; configured by Portal Sanctum FrontEnd; consumed by Server FrontEnd/Signserver.
- Server token: produced by Signserver; configured by Server FrontEnd; consumed by Client FrontEnd/Signclient/Signportal.
- Client token: produced by Signportal with Server and Signclient; configured by Client FrontEnd; validated against Server policy.
Sanitizing permissioned_routes
- Client-side permissioned_routes must be intersected with Server policy before signing/minting.
- Recommended pipeline:
- Client FrontEnd proposes routes (JSON string).
- Signclient parses and normalizes routes, removes disallowed entries, and reserializes to canonical JSON string (stable key order/whitespace if relevant to signatures).
- Signclient attaches sanitized permissioned_routes and computes serviceprovider_signature over the exact JSON used for minting.
FAQ
- Why are everything strings? Uniform serialization avoids on-chain schema migrations and simplifies strict signature verification by keeping a byte-identical representation.
Contact and licensing
- All rights reserved.
- Trials/evaluation:
rodit@rodit.org
- Next article: creating two intertwined root RODiT certificates for servers and clients.
- Separate article: fee/signature data format and signature generation.
Top comments (0)