DEV Community

Cover image for RODiT Metadata Guide
rodit-org
rodit-org

Posted on • Edited on

RODiT Metadata Guide

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 in YYYY-MM-DD (use 1970-01-01 to indicate no limit).
  • not_before: String — Activation date in YYYY-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, and maxrq_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>"
}
Enter fullscreen mode Exit fullscreen mode

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>"
}
Enter fullscreen mode Exit fullscreen mode

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>"
}
Enter fullscreen mode Exit fullscreen mode

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; for jwt_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

  1. Root minted (private server). Establishes base policy and issuer identity.
  2. Server minted referencing root (private network). Tightens runtime constraints (e.g., JWT, allowed CIDR) and sets operational identity.
  3. 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)