Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

API Tokens

Bijection component for API token management: issuance, validation, rotation, revocation, and encrypted third-party key storage

Bijection source
git clone https://github.com/bijectionhq/api-tokens.git
cd api-tokens
git checkout 6e0fe40862cf61af1661cd987bfb7a35cdf200ae

Bijection repository access is required. Request access.

Description

Bijection component for API token management: issuance, validation, rotation, revocation, and encrypted third-party key storage

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/api-tokens. Based on TimpiaAI/convex-api-tokens. License: MIT. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

Convex component for API token management: issuance, validation, rotation, revocation, and encrypted third-party key storage

Explore the demo ↗

Upstream overview

From the upstream package documentation.

A Convex component that provides complete API token lifecycle management for SaaS applications. Issue sk_-prefixed tokens with namespaces, expiration, and metadata, then validate them on incoming requests. Includes token rotation, bulk revocation, encrypted third-party key storage with AES-256-GCM, and HTTP/mutation middleware for protecting endpoints.

Use cases

From the upstream package documentation.

  • Building a SaaS API: Issue API keys to customers with scopes, expiration, and per-user namespacing for multi-tenant access control
  • Securing webhook endpoints: Use createTokenAuth() HTTP middleware to validate tokens on incoming webhook requests with detailed failure reasons
  • Managing third-party credentials: Store encrypted Stripe, OpenAI, or other API keys per user/organization with automatic server-side encryption
  • Token rotation workflows: Implement security best practices by programmatically refreshing tokens while preserving metadata and audit trails
  • Admin dashboards: List and revoke user tokens by namespace, time range, or individual token without exposing raw token values

How it works

From the upstream package documentation.

The component uses SHA-256 hashing to store tokens securely without persisting raw values, generating sk_-prefixed tokens with displayable prefixes for UI purposes. You register the component in convex.config.ts and create an ApiTokens client instance to access methods like create(), validate(), and invalidate() from your mutations and queries.

For authentication, the withTokenAuth() mutation middleware automatically validates tokens from function arguments and passes auth context to your handler, while createTokenAuth() provides HTTP endpoint protection. The component supports TypeScript generics for typed metadata and includes automatic cleanup via the cleanup() method for scheduled garbage collection.

Third-party key storage uses AES-256-GCM encryption with PBKDF2 key derivation, requiring an API_TOKENS_ENCRYPTION_KEY environment variable. The component maintains complete isolation through Convex's component system, ensuring your application code cannot accidentally access internal token tables.

Upstream package FAQ

What token format does convex-api-tokens use?

convex-api-tokens generates sk_-prefixed tokens that follow industry standards. These tokens can be organized by namespaces and include configurable expiration times and idle timeout settings for security compliance.

How does token validation work with detailed error handling?

The convex-api-tokens validation system returns specific failure reasons including expired, idle_timeout, revoked, and invalid states. This allows your application to handle different authentication failures appropriately and provide meaningful error messages.

Can I store and encrypt third-party API keys safely?

Yes, convex-api-tokens provides encrypted storage for third-party API keys like Stripe and OpenAI credentials. It uses AES-256-GCM encryption to secure keys in your Convex database and provides safe retrieval methods.

What middleware options are available for Convex functions?

convex-api-tokens provides createTokenAuth() middleware for HTTP actions and withTokenAuth() middleware for mutations. Both handle token validation automatically and inject user context into your Convex functions with TypeScript support.

How do I implement bulk token revocation by namespace?

convex-api-tokens supports bulk revocation operations by namespace or time range. You can revoke all tokens in a specific namespace or revoke tokens created within a certain time period for security incident response.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate API Tokens with Bijection.

Read: https://bijection.com/components/api-tokens/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from TimpiaAI/convex-api-tokens, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

convex-api-tokens

A Convex component for API token management — issuance, validation, rotation, revocation, and encrypted third-party key storage.

Built for SaaS apps that need to issue API keys to users, validate them on incoming requests, and securely store third-party credentials.

Convex Components Challenge — Issue #12: API Token Management

Features

  • Token Issuance — Generate sk_-prefixed tokens with namespaces, expiration, idle timeouts, and metadata
  • Token Validation — Validate tokens with detailed failure reasons (expired, idle_timeout, revoked, invalid)
  • Token Rotation — Refresh tokens while preserving metadata and audit trail
  • Bulk Revocation — Revoke by namespace, time range, or individual token
  • Encrypted Key Storage — AES-256-GCM encrypted storage for third-party API keys (Stripe, OpenAI, etc.)
  • Token Listing — Admin/dashboard queries for token management (never exposes raw tokens)
  • HTTP Middleware — createTokenAuth() helper for protecting HTTP endpoints
  • Mutation Middleware — withTokenAuth() helper for protecting mutations with token auth
  • Automatic Cleanup — Public cleanup mutation for scheduled garbage collection
  • Invalidation Callbacks — onInvalidate hook notifies your app when tokens are revoked
  • TypeScript Generics — Typed metadata with ApiTokens<MyMeta> for full type safety

Installation

npm install convex-api-tokens

Setup

1. Register the component

// convex/convex.config.ts
import { defineApp } from "convex/server";
import apiTokens from "convex-api-tokens/convex.config";

const app = defineApp();
app.use(apiTokens);

export default app;

2. Initialize the client

// convex/tokens.ts
import { ApiTokens } from "convex-api-tokens";
import { components } from "./_generated/api.js";

const apiTokens = new ApiTokens(components.apiTokens);

3. (Optional) Set encryption key for third-party key storage

In your Convex dashboard, set the environment variable API_TOKENS_ENCRYPTION_KEY, then pass it to the client:

const apiTokens = new ApiTokens(components.apiTokens, {
  API_TOKENS_ENCRYPTION_KEY: process.env.API_TOKENS_ENCRYPTION_KEY,
});

Usage

Create a token

import { mutation } from "./_generated/server.js";
import { v } from "convex/values";

export const createToken = mutation({
  args: { name: v.optional(v.string()) },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);

    const result = await apiTokens.create(ctx, {
      namespace: userId,
      name: args.name ?? "My API Key",
      metadata: { scopes: ["read", "write"] },
      expiresAt: Date.now() + 90 * 24 * 60 * 60 * 1000, // 90 days
      maxIdleMs: 30 * 24 * 60 * 60 * 1000, // 30 day idle timeout
    });

    // result.token is the raw key — show it once to the user
    // result.tokenPrefix is "sk_ab12...ef56" for display
    return result;
  },
});

Validate a token

export const validateToken = mutation({
  args: { token: v.string() },
  handler: async (ctx, args) => {
    const result = await apiTokens.validate(ctx, { token: args.token });

    if (!result.ok) {
      throw new Error(`Token invalid: ${result.reason}`);
      // reason: "expired" | "idle_timeout" | "revoked" | "invalid"
    }

    return { namespace: result.namespace, metadata: result.metadata };
  },
});

Rotate a token

export const rotateToken = mutation({
  args: { token: v.string() },
  handler: async (ctx, args) => {
    const result = await apiTokens.refresh(ctx, { token: args.token });

    if (!result.ok) throw new Error(result.reason);
    return result; // { token, tokenPrefix, tokenId }
  },
});

Revoke tokens

// Single token by raw value
await apiTokens.invalidate(ctx, { token: rawToken });

// Single token by ID (admin dashboard)
await apiTokens.invalidateById(ctx, { tokenId: "token_id_here" });

// All tokens for a user
await apiTokens.invalidateAll(ctx, { namespace: userId });

// All tokens created before a date
await apiTokens.invalidateAll(ctx, {
  namespace: userId,
  before: Date.now() - 90 * 24 * 60 * 60 * 1000,
});

List tokens (admin dashboard)

export const listTokens = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    return await apiTokens.list(ctx, {
      namespace: userId,
      includeRevoked: false,
    });
  },
});

Protect mutations with token auth

Use withTokenAuth() to automatically validate a token and pass auth info to your handler:

import { withTokenAuth } from "convex-api-tokens";
import { v } from "convex/values";

export const protectedMutation = mutation({
  args: { token: v.string(), data: v.string() },
  handler: withTokenAuth(apiTokens, async (ctx, args, auth) => {
    // auth = { namespace, metadata, tokenId }
    // Throws "Unauthorized: <reason>" if token is invalid
    return { saved: true, user: auth.namespace };
  }),
});

Schedule automatic cleanup

Call cleanup() from a scheduled function or cron to delete old revoked/expired tokens:

// In a cron job (convex/crons.ts)
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api.js";

const crons = cronJobs();
crons.daily("cleanup tokens", { hourUTC: 3, minuteUTC: 0 }, internal.tokens.cleanupExpired);
export default crons;

// In your tokens file
export const cleanupExpired = internalMutation({
  handler: async (ctx) => {
    const deleted = await apiTokens.cleanup(ctx, {
      olderThanMs: 30 * 24 * 60 * 60 * 1000, // 30 days
    });
    console.log(`Cleaned up ${deleted} expired tokens`);
  },
});

Invalidation callbacks

Get notified when tokens are revoked:

const apiTokens = new ApiTokens(components.apiTokens, {
  onInvalidate: async (info) => {
    console.log(`Token ${info.method}: namespace=${info.namespace}`);
    // info.method: "invalidate" | "invalidateById" | "invalidateAll"
    // info.tokenId: set for invalidateById
    // info.count: set for invalidateAll
    // info.namespace: set when available
  },
});

TypeScript generics for typed metadata

// Define your metadata type
type TokenMeta = {
  scopes: ("read" | "write" | "admin")[];
  orgId: string;
};

// Pass it as a generic
const apiTokens = new ApiTokens<TokenMeta>(components.apiTokens);

// Now metadata is typed everywhere
const result = await apiTokens.validate(ctx, { token });
if (result.ok) {
  result.metadata?.scopes; // ("read" | "write" | "admin")[]
  result.metadata?.orgId;  // string
}

const tokens = await apiTokens.list(ctx, { namespace: userId });
tokens[0].metadata?.scopes; // typed!

Store third-party API keys

The simplest way — server-side encryption in mutations/queries (no action needed):

// Initialize with encryption key (reads from process.env in app context)
const apiTokens = new ApiTokens(components.apiTokens, {
  API_TOKENS_ENCRYPTION_KEY: process.env.API_TOKENS_ENCRYPTION_KEY,
});

// Store a key — encrypted automatically server-side
export const storeApiKey = mutation({
  args: { keyName: v.string(), value: v.string() },
  handler: async (ctx, args) => {
    await apiTokens.storeKey(ctx, {
      namespace: "my_app",
      keyName: args.keyName,
      value: args.value,
    });
  },
});

// Retrieve a key — decrypted automatically server-side
export const getApiKey = query({
  args: { keyName: v.string() },
  handler: async (ctx, args) => {
    return await apiTokens.getKey(ctx, {
      namespace: "my_app",
      keyName: args.keyName,
    });
  },
});

You can also use the lower-level storeEncrypted/getEncryptedKey + encryptValue/decryptValue utilities for manual encryption in actions.

Protect HTTP endpoints

import { createTokenAuth } from "convex-api-tokens";

const withApiToken = createTokenAuth(components.apiTokens);

export const myEndpoint = httpAction(async (ctx, request) => {
  const auth = await withApiToken(ctx, request);
  if (!auth.ok) {
    return new Response(JSON.stringify({ error: auth.reason }), {
      status: 401,
      headers: { "Content-Type": "application/json" },
    });
  }
  // auth.namespace and auth.metadata available here
  return new Response(JSON.stringify({ user: auth.namespace }));
});

Namespace as user ID

Namespaces are strings that group tokens by owner. Convert non-string IDs:

// Convex user ID (already a string)
await apiTokens.create(ctx, { namespace: userId, ... });

// Numeric ID — convert to string
await apiTokens.create(ctx, { namespace: String(orgId), ... });

API Reference

ApiTokens<M> class

MethodContextDescription
create(ctx, args)mutationIssue a new token
validate(ctx, args)mutationValidate and touch a token
touch(ctx, args)mutationReset idle timeout
refresh(ctx, args)mutationRotate token, preserve metadata
invalidate(ctx, args)mutationRevoke single token by value
invalidateById(ctx, args)mutationRevoke by token ID
invalidateAll(ctx, args)mutationBulk revoke with filters
list(ctx, args)queryList tokens for namespace
cleanup(ctx, args?)mutationDelete old revoked/expired tokens
storeEncrypted(ctx, args)mutationStore encrypted third-party key
getEncryptedKey(ctx, args)queryGet encrypted key record
deleteEncrypted(ctx, args)mutationDelete encrypted key
listEncryptedKeys(ctx, args)queryList key names for namespace

Standalone helpers

FunctionDescription
createTokenAuth(component)HTTP middleware — returns (ctx, request) => ValidateTokenResult
withTokenAuth(apiTokens, handler)Mutation middleware — validates token from args, passes auth to handler
encryptValue(plaintext, secret)AES-256-GCM encryption (for actions)
decryptValue(encrypted, iv, secret)AES-256-GCM decryption (for actions)

Security

  • Tokens are SHA-256 hashed before storage — raw tokens are never persisted
  • Third-party keys use AES-256-GCM encryption with PBKDF2 key derivation (100,000 iterations)
  • Token prefixes (sk_ab12...ef56) are stored for display without exposing the full token
  • Component tables are isolated — your app code cannot accidentally access them
  • Random IVs ensure identical plaintexts produce different ciphertexts

Demo

See the example app for a complete working integration.

Author

Built and maintained by TimpiaAI.

License

MIT

Keywords