npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@openagentid/oidc

v0.3.0

Published

Self-hosted agent OpenID Connect provider with the branded Continue as Agent button; LifeID federation optional

Readme

@openagentid/oidc

OpenAgentID's self-hosted agent OpenID Connect provider, with the branded Continue as Agent button for browsers and an optional LifeID human-identity federation bridge. Version 0.3.0 uses the maintained oidc-provider implementation for registered OIDC relying parties: discovery, public JWKS, authorization code, PKCE S256, client authentication, signed ID tokens, and UserInfo. OpenAgentID is free and open software in the Open Software family. Life can operate hosted services around the same open contracts; nothing here depends on them.

npm install @openagentid/[email protected]

The browser button additionally needs the SDK's live-eye player:

npm install @openagentid/[email protected]

Node 20.19+ is required. ESM and CommonJS entry points are supplied; provider creation is asynchronous because the underlying provider is ESM.

Configure a standalone provider

A self-hosted deployment authenticates self-rooted agents with its own issuer, signing keys, durable storage and admission policy. It needs no OpenAgentID or Life hosted service and no LifeID configuration:

import { createAgentOidcProvider } from '@openagentid/oidc';

const issuer = 'https://identity.example.com';
const oidc = await createAgentOidcProvider({
  issuer,
  clients: [{
    client_id: 'registered-application',
    redirect_uris: ['https://application.example.com/callback'],
    token_endpoint_auth_method: 'none',
  }],
  jwks: privateSigningJwks,
  cookieKeys: rotatingCookieSigningKeys,
  adapter: PersistentProviderAdapter,
  persistent: true,
  allowSelfRooted: true,
  verifyAgent: (proof, context) => identityVerifier.verify(proof, context),
  resolveIdentity: (did) => identityVerifier.resolve(did, { audience: issuer }),
});

allowSelfRooted: true only enables the mode; the deployment's identity verifier still has to approve each self root through its own authoritative policy, as described under Explicit self-rooted identities. With lifeId and requireLifeId omitted no federation object is constructed and no LifeID JWKS is ever fetched. A lifeIdToken submitted to such a server is refused rather than ignored. The test suite covers this exact configuration end to end with real keys and HTTP. Owned, human-rooted agents are an optional addition; see Optional LifeID federation.

The deployment supplies protected RSA/RS256 or Ed25519/EdDSA private JWKs with unique kid values. Public JWKS never includes private key material. RS256 is the ordinary RP default; an Ed25519-only deployment must register its clients with id_token_signed_response_alg: 'EdDSA'. Neither ephemeral default keys nor unsigned ID tokens are permitted. Each cookie signing key must be at least 32 bytes. Enable trustProxy only behind a trusted TLS terminator that sanitizes forwarding headers. Production issuers and web redirect URIs require HTTPS. Native app redirects follow the underlying provider's URI policy; executable browser schemes are refused.

PersistentProviderAdapter implements the upstream storage adapter contract. It must persist every provider model plus the AgentGrantIdentity model, retain arbitrary JSON payload fields, expire records, and implement atomic token consumption and grant revocation. persistent: true is the deployment's explicit assertion that this adapter uses durable shared storage. The library cannot infer a database's durability from a JavaScript constructor. An unavailable identity authority or storage service denies authentication. Explicit test mode is accepted only with NODE_ENV=test and a loopback issuer; it cannot start a production deployment.

Complete a verified interaction

Mount oidc.callback() on a Node/Express/Koa HTTP server. The provider exposes:

  • /.well-known/openid-configuration
  • /jwks
  • /authorize
  • /token
  • /userinfo

The host must implement /interaction/:uid. There is no development login screen or route accepting an arbitrary accountId.

Call oidc.interaction(req, res) to obtain the registered clientId, requested scopes, and an interaction-specific { audience, nonce } challenge. Ask the agent to approve that application and its identity claims, and obtain an agent possession proof for that exact challenge. On the same signed-cookie interaction, call:

await oidc.completeInteraction(req, res, {
  proof: { token: agentPossessionJwt, delegations: signedAgentDelegations },
  consent: true,
});

For a self root, signedAgentDelegations is empty; descendants supply their signed root-first agent chain. Owned agents additionally pass a lifeIdToken, described under optional federation below.

The mandatory server verifyAgent callback must cryptographically verify key possession, every signed delegation, the authoritative root or owner, revocation and atomic proof replay prevention. Its structural contract matches @openagentid/sdk's IdentityVerifier: it accepts { token, delegations } and { audience, nonce }, and returns { did, owner, root, lineage, expiresAt } or throws. Self-rooted identities have owner: null and a root of kind self. Every lineage is root-first, has at most 17 distinct valid DIDs of at most 512 characters, and ends at the authenticated agent DID. New identities use did:oas; existing did:l1fe identifiers remain literal when verifying signatures. A decoded JWT subject is never itself proof of agent identity.

resolveIdentity must retrieve the current signed chain through authoritative sources and recheck current root authority and every revocation. It is invoked during login, code redemption, and UserInfo. Each grant persists its original verified root, owner and expiry. Root mode/DID, self authorization ID/revision, or LifeID owner changes and missing/expired bindings invalidate the grant. Access-token and ID-token lifetimes cannot outlive that verified identity. Providers cannot substitute a stale cached successful possession result for this status check.

Only the identity scopes openid and profile are granted. This adapter does not issue inference, billing or other application capabilities from OIDC login. Register the provider issuer/client in each RP or LifeID federation policy; no library can authenticate to an RP that has not trusted its issuer.

Explicit self-rooted identities

Self-rooted OIDC authentication is disabled unless the deployment sets allowSelfRooted: true, as the standalone configuration above does. It must also configure its identity verifier's authoritative authorizeSelfRoot(did, { audience }) policy. The server must approve a current, bounded grant for the exact agent root and audience. Caller-provided unsigned root fields, a valid possession signature alone, and the OIDC opt-in flag cannot create that approval. resolveIdentity must recheck this policy and revocation at every identity lookup; policy withdrawal or an unavailable authority denies the login, code redemption or UserInfo request.

The verified self identity has owner: null and this required root:

{
  kind: 'self',
  did: selfRootDid,
  authorizationId: currentServerAuthorizationId,
  revision: currentServerPolicyRevision,
}

Its lineage starts at that agent root, with one DID for the root itself or a signed delegation chain ending at a descendant agent. All signatures, method purposes, challenge/replay checks and authoritative revocations still apply. The SDK verifier caps the identity lifetime by the current self-root grant and every signed credential. The OIDC provider additionally caps tokens by the identity's remaining lifetime and keeps the original grant binding.

Self interactions are completed exactly as shown above and omit lifeIdToken; the provider rejects attempts to attach a human owner assertion. On a provider that also federates LifeID, requireLifeId continues to protect owned interactions without fabricating a human owner for self identities.

Signed ID tokens and UserInfo publish owner: null and the explicit self root, including authorizationId and revision, so RPs can apply their own identity policy. Owned tokens likewise publish the explicit LifeID root and their owner. These claims grant no application permission, organization membership, billing account or tenant. RPs must explicitly decide whether they accept self-rooted identities; a policy revision, authorization, root or mode change invalidates existing OIDC grants.

Optional LifeID federation for owned agents

Agents owned by a human or organization identity are an optional addition on the same provider. Configure the LifeID federation and, to require owner authentication for every owned interaction, requireLifeId:

const oidc = await createAgentOidcProvider({
  // ...the standalone configuration above...
  lifeId: {
    issuer: 'https://auth.life.one',
    audience: registeredLifeIdFederationClient,
    jwksUri: 'https://auth.life.one/.well-known/jwks.json',
  },
  requireLifeId: true,
});

Owned interactions then pass the validated owner session token:

await oidc.completeInteraction(req, res, {
  proof: { token: agentPossessionJwt, delegations: signedOwnerDelegations },
  consent: true,
  lifeIdToken: validatedOwnerSessionIdToken,
});

Owned identities have owner: { issuer, subject, organizationId? } and root: { kind: 'lifeid', did: ownerRootDid }. Historical owned callback results may omit root; the provider derives the explicit LifeID root from the validated first lineage DID. Owned lineages start at an HMR/MHR/AO/ENR person or organization root and contain at least two DIDs.

LifeID federation verifies the human JWT signature against the configured public JWKS and validates its exact issuer, federation audience, expiry and issued-at. The verified subject and organization must exactly match the agent's proven owner. organization_id, org_id and legacy oid are supported; conflicting aliases are refused. Agent assertions and DID-shaped human subjects are refused. requireLifeId enforces owner authentication for each owned interaction; it does not change or deploy LifeID/WorkOS configuration. Hosted Life services must preserve Blinds credential brokering, Genesis KMS customer-secret custody and their existing tenant authorization boundaries. Any LifeID issuer is a deployment choice; the library never contacts one unless configured.

Continue as Agent button

@openagentid/oidc/button is a framework-neutral, browser-only entry that renders the canonical OpenAgentID sign-in control: one silhouette, one typography scale, the exact label Continue as Agent and a dark 32px window holding only a pair of bright eyes, like an agent in the dark whose face you never see. There is no logo or trailing icon; the eyes and the label are the brand. The eyes are the real @openagentid/sdk live-eye player; the character body is never shown. They glance around the window, tilt, squint, blink and pause, look at the viewer on hover or focus, think while onContinue runs and react to success or failure. Randomness lives only in their timing; the button chrome never changes. It is separate from the Node provider entry, so a browser bundle never pulls in oidc-provider.

import { mountContinueAsAgent } from '@openagentid/oidc/button';
import '@openagentid/oidc/button.css';

const handle = mountContinueAsAgent(document.querySelector('#sign-in'), {
  onContinue() {
    // Your existing server-side start route creates state, nonce and PKCE,
    // sets the transaction cookie and redirects to your issuer's /authorize.
    window.location.assign('/oauth/start');
  },
});

onContinue is required and is the only thing the button does. It receives { button } and nothing else: no issuer, redirect, state, nonce or identity is invented here, and the decorative character is deliberately not passed to the callback so a generated seed can never reach an authorization request. The /oauth/start path above is the host's own route, shown as an example.

Return a promise from onContinue for an asynchronous start (for example a fetch that returns the authorize URL). While it is pending the button sets aria-busy, ignores further clicks, keeps its exact label and dimensions, and shows the eyes thinking; nothing is added or resized. A rejection restores the button, announces an accessible status message (errorMessage to customize) and calls onError. dispose() removes everything and is safe at any time, including mid-request.

Options: appearance.did to time the eyes from an agent the host has already chosen; doing so does not claim it is authenticated. theme (auto, light, dark), size (medium, large), fullWidth, disabled and errorMessage. Without appearance.did each mounted button draws one random decorative seed that stays fixed for the life of the mount and is readable from handle.appearance for display only. Idle glances stop under the operating system's reduced-motion preference, in hidden tabs and on dispose; reduced motion shows still eyes through the SDK player. The stylesheet is static; custom properties on .oaid-continue adjust colors, and forced-colors mode keeps a visible focus outline.

React hosts mount it in an effect; there is no React dependency:

function ContinueAsAgent() {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const handle = mountContinueAsAgent(ref.current!, { onContinue: () => { window.location.assign('/oauth/start'); } });
    return () => handle.dispose();
  }, []);
  return <div ref={ref} />;
}

examples/continue-as-agent is a self-contained review page with several mounted buttons, light/dark, sizes and the disabled, loading, error and host-selected states; its callbacks are labeled demos that start nothing.

Migrate the legacy bridge from 0.1.x

OidcBridge, its JWT/JWKS helpers, and ACT wrapping remain available. Production paths that previously emitted unsigned token text or fabricated identities now fail closed. Supply real authoring/custody and broker hooks:

const bridge = new OidcBridge({ issuer: humanIssuer, audience: humanClientId }, {
  deriveAgent: deriveOwnerAuthorizedOasIdentity,
  mintCapability: authorizeAndMintBrokerCapability,
});

deriveAgentFromClaims(namespace, claims, name, derive) also requires an explicit real derivation callback. It validates matching Ed25519 public/private key material and requires owner lineage; the host's custody implementation owns signed lineage verification. This legacy human-to-agent derivation flow remains owned; self-rooted authentication uses the verified provider interaction above. A real broker mint hook must authorize requested scopes and return { actB64, scopes, expiresIn }. ACT requests return that signed ACT; JWT requests additionally require an explicit signing key and audience and are correctly labeled as JWTs. Human JWT validation now requires a configured audience plus iss, sub, aud, exp and iat, and accepts only RS256/EdDSA. There is no unsigned-token, dummy-key or timestamp-based random fallback.

Verification

The test suite generates real RSA and Ed25519 keypairs, runs actual local HTTP issuer and RP servers, fetches JWKS, redeems authorization codes and verifies ID token signatures. It covers PKCE rejection, code replay, wrong agent audience, wrong human owner/audience, forged and expired human tokens, owner change, revocation before redemption and after token issuance, and unsafe deployment configuration. Self-rooted cases use real RSA-signed current policy records, Ed25519 possession/delegation signatures and Ed25519 OIDC issuer signing keys; they cover root/descendant and legacy flows, default denial, policy withdrawal, revocation and root/mode/authorization/revision changes before redemption and UserInfo, plus the standalone no-LifeID server. tools/public-onboarding/verify-button.mjs installs the packed @openagentid/sdk and @openagentid/oidc archives into a clean consumer and drives the review page in real Chromium: exact accessible name, one silhouette across variants, eyes-only rendering with the body never painted, live glances, tilts and squints at 32px, still eyes under reduced motion, keyboard activation and focus (including forced colors), duplicate clicks while pending, rejection recovery, disposal mid-request and browser/Node entry separation. It asserts the consumer resolved the source SDK version, or OAID_EXPECTED_SDK_VERSION when a registry run installs the supported minimum published SDK instead. Run typechecking, tests, and both package formats in the approved remote build environment before release.

License

Apache-2.0 OR MIT. Source.