# Authenord agent and receiver SDKs — 0.1.0

Two dependency-free JavaScript ES modules and an Authenord HTTP exchange profile.
Initial developer preview. Node.js 22+ is the supported server runtime. The agent
module also uses standard Web Crypto/Fetch APIs; other runtimes require validation
of Ed25519 support. This release is available by direct download, not npm.

## Get started

Download https://authenord.com/sdk/authenord-sdk-0.1.0.zip and extract it.
Keep agent.mjs, verifier.mjs and protocol.mjs together. No dependency installation
is needed. Type declarations and runnable Node examples are included.

1. Register an agent and its Ed25519 public key at https://authenord.com/#workspace.
2. Issue and download its credential. Retain the matching private-key file locally.
3. Configure a receiver with an allowed Agent ID and a persistent replay store.
4. Sign requests using the agent SDK; validate them with the receiver SDK.

## Agent SDK

```js
import { createAgent } from './agent.mjs';
const agent = createAgent({
  credential, // parsed downloaded signed credential (or {credential: ...})
  privateKey, // CryptoKey, base64url PKCS8, or {private_key_pkcs8: ...}
  allowedOrigins: ['https://supplier.example'],
});
const response = await agent.fetch('https://supplier.example/quotes', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ sku: 'ACME-1' }),
});
```

`signRequest(url, init)` returns a signed Fetch Request if your software has its own
transport. `fetch(url, init)` signs and sends using Fetch with `redirect: 'error'`.
Redirects must not be followed automatically. Re-sign a new request after independently
approving a destination. The caller must manage credential renewal and key rotation.
The SDK does not create accounts or store keys. The issuer private key is never needed.

Defaults: 60-second request lifetime, 1 MiB body limit, HTTPS only. Max lifetime is
120 seconds; configurable body limit is 0–16 MiB. Only explicit loopback development
can enable `allowLocalHttp`. Streaming requests are buffered within the size limit.
Do not use streaming uploads or compressed request bodies with this profile.

## Receiver SDK

```js
import { createVerifier, createMemoryReplayStore, withAuthenord } from './verifier.mjs';
const verifier = createVerifier({
  audience: 'https://supplier.example',
  replayStore: createMemoryReplayStore(), // local/single-process demonstration only
});
const handle = withAuthenord(
  verifier,
  (identity, request) => approvedAgentIds.has(identity.agentId)
    && request.method === 'POST' && new URL(request.url).pathname === '/quotes',
  async (request, identity) => Response.json({ receivedFrom: identity.agentId }),
);
// Pass the original Fetch Request to handle(request) at your server boundary.
```

`verify(request)` returns `{agentId, credentialId, registeredKeyId, declaredDomain,
issuer, credential, requestCreated, requestExpires, liveStatusChecked}` or throws
`AuthenordError` with a machine-readable `.code`. It leaves the original body readable.
The domain is a recorded claim, not a verified legal-company affiliation.

Default verification checks request signature/body integrity, freshness, destination,
registered-key possession, credential signature and live credential/issuer status via
Authenord's fixed API endpoint. It never resolves URLs from an untrusted credential.
No Supabase user session or service-role secret is required. The SDK contains only
the same public publishable key used by the website. Live checks are not cached.
Service errors and timeouts deny verification; no offline fallback grants access.

`withAuthenord(verifier, authorize, handler)` requires an explicit policy. Valid identity
alone does not grant access. It returns 401 for failed verification, 403 for policy
denial, and 503 for verification/replay service outages. Unexpected application errors
should be handled by your normal server error handler. Do not log full credentials,
private keys or the entire signed request.

### Replay protection is required

Provide a long-lived `replayStore.consume(key, expiresAtSeconds)` which atomically
reserves an unseen key until expiry and returns true only to the first caller.
Return false for duplicates; throw on outage. Store keys across workers, regions and
restarts. A Redis adapter can implement `SET key 1 NX EX ttl` as a single atomic command;
use a dedicated namespace and retain through the supplied absolute expiry.

The included memory store has a 10,000-entry limit and never evicts unexpired entries
to admit new requests. It is suitable only for tests or one-process demonstrations:
restart or separate instances lose/share no history. Do not instantiate it per request.
Middleware consumes a nonce before application authorization/handling. Retried requests
must be newly signed; use an application idempotency policy to prevent duplicate actions.

### Proxy and framework integration

Verify before body parsing, decompression, path/query rewrites or action execution.
Build the Fetch Request using a configured public origin and exact public request path
and query. Do not trust arbitrary Host/X-Forwarded-* headers. Configure trusted proxies
explicitly if external and internal URLs differ. Do not act on unsigned headers as
authenticated claims; this profile signs only the components listed in EXCHANGE.md.
Preserve original request bytes; content-encoding is not supported.

Cross-origin browser agents require the receiving service to handle unauthenticated
OPTIONS preflight and explicitly allow Content-Type, Content-Digest, Authenord-Version,
Authenord-Credential, Signature-Input and Signature headers for approved origins. CORS
is a receiver configuration, not an Authenord account permission.

### Trust adapters

`createCredentialChecker({fetch, timeoutMs: 5000})` calls the fixed Authenord verifier.
`createVerifier({checkCredential})` permits a privileged custom trust adapter. That
adapter MUST verify the issuer signature and live credential/issuer status and return
`{valid:true, signature_valid:true, credential_status:'ACTIVE', issuer_key_status:'ACTIVE',
expired:false}` only after those checks. Never replace it with `()=>({valid:true,...})`.
An offline signature check alone is insufficient for this online exchange profile.

## Run the example against real registered credentials

In one terminal (replace the sample Agent ID with your registered ID):

```sh
ALLOWED_AGENT_ID=agent_YOUR_REGISTERED_ID node examples/receiver.mjs
```

PowerShell: `$env:ALLOWED_AGENT_ID='agent_YOUR_REGISTERED_ID'; node examples/receiver.mjs`.
In another terminal:

```sh
node examples/agent.mjs /path/to/credential.json /path/to/private-key.json
```

This local demo still checks live credential status through Authenord. No credentials,
API sessions or private keys are included in the download. For an entirely synthetic
local demonstration, run `node examples/local-demo.mjs`; its test issuer is deliberately
trusted only inside that process and its credentials cannot pass the public verifier.

## Exchange and discovery

- Protocol: https://authenord.com/sdk/v0.1.0/EXCHANGE.md
- Discovery: https://authenord.com/.well-known/authenord.json
- API description: https://authenord.com/sdk/openapi.json
- Agent-readable guide: https://authenord.com/llms.txt
- Human guide: https://authenord.com/#developers

Profile 0.1 is an Authenord-specific, strict subset of RFC 9421, with RFC 9530
Content-Digest. It is not an IETF standard, generic RFC 9421 implementation, OAuth token,
TLS client certificate or a W3C VC interoperability claim. Independent implementations
must follow EXCHANGE.md exactly. Existing sites must integrate a receiver to recognise it.

MIT license. Initial developer preview; not independently security audited. Validate your
authorization, deployment, replay store, proxy handling and operating limits before use.
