Bring your own token store

Bring your own token store

Your A2A tokens stay in the store you register. The PWA stores them in localStorage by default. Server or enterprise deploys can swap that for a backend they control — file, Keychain, Vault, KMS, HSM — as long as it passes the conformance suite.

The interface

One typed contract. Hash-only storage. All methods async so backends can hit disk, IPC, or the network.

interface A2ATokenStore {
  readonly kind: string;
  list(workspaceId): Promise<A2AClientToken[]>;
  issue(workspaceId, input): Promise<{ client, rawToken }>;
  revoke(workspaceId, id): Promise<void>;
  verify(workspaceId, rawToken, now?): Promise<A2AClientToken | null>;
}

Register your backend at boot

import { configureA2ATokenStore } from "<aarmos-runtime>/a2a/token-store";
import { createEncryptedFileStore } from "<aarmos-runtime>/a2a/adapters/encrypted-file";

configureA2ATokenStore(await createEncryptedFileStore({
  dir: "/var/lib/aarmos/a2a",
  passphrase: process.env.AARMOS_A2A_PASSPHRASE!,
}));

Everything downstream — the settings UI, the A2A adapter, verifyA2AToken — routes through the store you registered. There is no fallback path that writes to disk behind your back.

Writing your own adapter

Most adapters only need to persist and load a list of records. Use makeRecordStore and you inherit the issue / revoke / verify / expiry / hashing logic for free:

import { makeRecordStore } from "<aarmos-runtime>/a2a/token-store";

export const myStore = makeRecordStore({
  kind: "my-backend",
  load: (ws) => myBackend.get(ws),      // returns A2AClientToken[]
  save: (ws, clients) => myBackend.put(ws, clients),
});

Conformance suite (mandatory)

Every adapter — ours or yours — must pass the same suite. It pins the invariants that make BYOS safe: round-trip, verify, expiry, tenant isolation, revocation, and hash-only storage.

import {
  runA2ATokenStoreConformance,
  formatConformanceResults,
} from "<aarmos-runtime>/a2a/conformance";

const results = await runA2ATokenStoreConformance(() => myStore);
console.log(formatConformanceResults(results));
if (results.some((r) => !r.ok)) process.exit(1);

What we ship in-box

  • localStorage — default, browser / PWA.
  • encrypted-file — Node, AES-GCM + PBKDF2-SHA256 (200k iters). Same primitives as the on-device vault.
  • memory — ephemeral, for tests.

Keychain, Vault, KMS, and HSM integrations can use the interface above with an adapter you supply. Aarmos-provided adapters for these stores are not currently available; any Enterprise implementation scope would be defined during contracting.