Mint API keys as SHA-256-hashed tokens in Convex with TTL, revocation, and scopes — raw value returned once, only the digest persisted.
npm install @vllnt/convex-tokensSecret Tokens is a hashed-secret token primitive for Convex: mint a raw token against an opaque `resourceRef`, return it exactly once, and store only its SHA-256 (or SHA-512) digest — so a compromised database row yields no usable credential. TTLs are server-enforced and clamped into `[MIN_TTL_MS, maxTtlMs]`, revocation is a hard state transition, and the management surface (`list`, `getMetadata`) never exposes the hash. Token records live in sandboxed component tables (zero blast radius into your schema), the host owns all identity resolution and passes an opaque `resourceRef`; the component runs identically on Convex Cloud and self-hosted convex-backend.
The @vllnt/convex-tokens component provides a mint/validate/revoke primitive for Convex. Call tokens.mint(ctx, { resourceRef: userId }) in a mutation to get { token, id }, show the raw token once, then call tokens.validate(ctx, rawToken) in a query to verify it. The component stores only the SHA-256 hash, so the raw key is never recoverable after the initial mint.
@vllnt/convex-tokens enforces expiry by sourcing time from the server clock and clamping any requested ttlMs into a configurable [MIN_TTL_MS, maxTtlMs] range. This means neither the client nor a compromised request can extend or forge a token's expiry. Expired tokens are swept automatically by a built-in cron via tokens.prune().
Call tokens.revoke(ctx, rawToken, scope?) in a mutation to transition a token to revoked state before it expires. The method returns true if the token was successfully transitioned on this call. The management surface (list, getMetadata) never returns the hash, so revocation can be audited safely.
@vllnt/convex-tokens supports an optional scope parameter on mint, validate, revoke, and list. Scopes let you namespace tokens per tenant, purpose, or locale so that a token minted in one scope cannot be validated against another. The default scope is global when no scope is provided.
No. @vllnt/convex-tokens hashes the raw token client-side using Web Crypto (SHA-256 by default, SHA-512 configurable) before any database write. Only the digest is persisted. The raw token is returned exactly once from tokens.mint() and is never recoverable afterward, so a compromised database row cannot be replayed.
Expiry is controlled entirely by the server. When minting, a requested ttlMs is clamped server-side into the configured [MIN_TTL_MS, maxTtlMs] range. The expiresAt timestamp and the now value used during validation both come from the server clock, so a client cannot forge or extend a token's lifetime.
Run npm install @vllnt/convex-tokens (peer dependency: convex@^1.41.0). In convex/convex.config.ts, import the config with import tokens from '@vllnt/convex-tokens/convex.config' and register it with app.use(tokens). Then instantiate the client in your function files with const tokens = new Tokens(components.tokens).
The useTokens hook from @vllnt/convex-tokens/react wraps the host application's own re-exported list query and returns TokenMetadata[] or undefined. It never returns a raw token or the stored hash. The hook accepts an optional scope and resourceRef for filtering.
@vllnt/convex-tokens is auth-agnostic. The component never authenticates the caller. The host application is responsible for resolving identity and passing an opaque resourceRef string into mint and the management methods. This means it works alongside any auth system you already use with Convex.