Skip to content

fix: split API/WS auth tokens and add rotation/revocation#122

Merged
teng-lin merged 3 commits intomainfrom
fix/token-scope-separation-api-ws
Feb 22, 2026
Merged

fix: split API/WS auth tokens and add rotation/revocation#122
teng-lin merged 3 commits intomainfrom
fix/token-scope-separation-api-ws

Conversation

@teng-lin
Copy link
Copy Markdown
Owner

@teng-lin teng-lin commented Feb 22, 2026

Summary

Addresses architecture review finding in docs/review/architecture-review-2026-02-22.md:

  • Single long-lived token reused across API and WebSocket auth

This PR separates API and WebSocket token scopes, adds token lifetime/rotation controls, and revokes tokens on shutdown.

What Changed

  • Split auth scope:
    • API auth token is separate from consumer WebSocket token.
    • Browser receives both via distinct meta tags.
  • Added RotatingTokenAuthority:
    • TTL-based expiry
    • periodic rotation
    • bounded active-token window
    • revokeAll() support
  • Startup wiring (src/bin/beamcode.ts):
    • API token: rotates every 60m, TTL 12h
    • WS token: rotates every 15m, TTL 60m
  • HTTP auth (src/http/server.ts):
    • supports validator-based Bearer validation (apiKeyValidator) in addition to static key mode
  • WS auth (src/server/api-key-authenticator.ts):
    • supports either static token or validator callback
  • Consumer HTML (src/http/consumer-html.ts):
    • supports injecting both beamcode-api-token and beamcode-consumer-token
    • rebuilds from source HTML to avoid stale/duplicated tags
  • Web client:
    • API reads beamcode-api-token with legacy fallback to beamcode-consumer-token
    • WS includes scoped token query parameter from beamcode-consumer-token

Validation

Passed locally:

  • pnpm test -- src/server/rotating-token-authority.test.ts src/server/api-key-authenticator.test.ts src/http/server.test.ts src/http/consumer-html.test.ts
  • pnpm --dir web test -- src/api.test.ts src/ws.test.ts
  • pnpm typecheck

Compatibility Notes

  • Static API key behavior remains supported where used.
  • Existing pages still work via API-token legacy fallback.
  • A very old open tab may need refresh after WS token expiry to reconnect with a fresh token.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @teng-lin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's security posture by overhauling its authentication token management. It introduces a robust system for generating, rotating, and revoking distinct tokens for API and WebSocket communication, moving away from a single shared token. This change improves the resilience against token compromise and provides more granular control over access, ensuring that the application adheres to modern security best practices.

Highlights

  • Token Separation: API and WebSocket authentication tokens are now separated, addressing a security finding regarding single long-lived tokens.
  • Rotating Token Authority: Introduced a new RotatingTokenAuthority class to manage tokens with TTL-based expiry, periodic rotation, and bounded active windows.
  • Token Revocation: Implemented immediate token revocation on application shutdown for enhanced security.
  • HTML Token Injection: Updated the consumer HTML injection mechanism to support distinct meta tags for API and WebSocket tokens, with backward compatibility for legacy API tokens.
  • Authenticator Updates: The HTTP server and WebSocket authenticator now support a validator callback mode, allowing dynamic token validation.
Changelog
  • src/bin/beamcode.ts
    • Removed randomBytes import as token generation is now handled by RotatingTokenAuthority.
    • Imported RotatingTokenAuthority for token management.
    • Defined constants for API and consumer WebSocket token TTL and rotation intervals.
    • Replaced single consumer token generation with separate RotatingTokenAuthority instances for API and WebSocket tokens.
    • Updated injectConsumerToken call to injectConsumerAuthTokens to handle both API and WS tokens.
    • Configured ApiKeyAuthenticator for WebSocket connections to use the consumerWsTokens validator.
    • Modified createBeamcodeServer to use an apiKeyValidator callback instead of a static apiKey.
    • Updated console output to display the API token and token rotation information.
    • Added clearInterval calls for token rotation timers and revokeAll calls for token authorities during shutdown.
  • src/http/consumer-html.test.ts
    • Added new test suite for injectConsumerAuthTokens to verify injection of both API and consumer token meta tags.
    • Added a test to ensure injectConsumerAuthTokens updates existing tags rather than duplicating them.
  • src/http/consumer-html.ts
    • Introduced sourceHtml, cachedApiToken, and cachedConsumerToken variables to manage HTML content and injected tokens.
    • Refactored loadConsumerHtml to load from sourceHtml and rebuild injected content.
    • Added escapeHtmlAttribute utility function for safe HTML attribute values.
    • Implemented rebuildInjectedHtml to dynamically construct the HTML with current API and consumer tokens.
    • Replaced injectConsumerToken with a new injectConsumerAuthTokens function that accepts optional apiToken and consumerToken.
    • Provided injectApiToken and injectConsumerToken as wrappers around injectConsumerAuthTokens for specific token types.
  • src/http/server.test.ts
    • Updated startServer helper function to accept an optional apiKeyValidator callback.
    • Added a test case to verify that the server supports validator-based authentication for /api/* routes.
  • src/http/server.ts
    • Added apiKeyValidator property to the HttpServerOptions interface.
    • Modified createBeamcodeServer to destructure apiKeyValidator from options.
    • Updated the authentication logic to use apiKeyValidator if provided, falling back to timingSafeCompare with a static apiKey.
  • src/server/api-key-authenticator.test.ts
    • Added a test case to verify that ApiKeyAuthenticator correctly supports and uses a validator callback mode.
  • src/server/api-key-authenticator.ts
    • Introduced TokenValidator type for a function that validates a token.
    • Modified the ApiKeyAuthenticator constructor to accept either a static apiKey string or a TokenValidator function.
    • Updated the authenticate method to use the provided validator callback if available, otherwise falling back to hashing and timingSafeEqual.
  • src/server/rotating-token-authority.test.ts
    • Added a new test file for RotatingTokenAuthority.
    • Included tests for token rotation, validation, TTL expiry, handling of multiple active tokens, immediate revocation, and enforcement of maxActiveTokens.
  • src/server/rotating-token-authority.ts
    • Added a new file defining the RotatingTokenAuthority class.
    • Implemented token generation, storage, validation, and expiration logic.
    • Included methods for rotate, currentToken, validate, and revokeAll.
    • Added internal pruneExpired method to remove expired tokens.
  • web/src/api.test.ts
    • Updated beforeEach hook to inject beamcode-api-token meta tag for API authentication tests.
    • Added a test case to confirm that the API client falls back to the legacy beamcode-consumer-token meta tag if beamcode-api-token is not present.
  • web/src/api.ts
    • Modified getApiKey function to first look for beamcode-api-token and then fallback to beamcode-consumer-token for backward compatibility.
  • web/src/ws.test.ts
    • Added document.head.innerHTML setup in beforeEach and teardown in afterEach to manage the beamcode-consumer-token meta tag for WebSocket tests.
    • Added an assertion to verify that the WebSocket connection URL includes the token query parameter from the beamcode-consumer-token.
  • web/src/ws.ts
    • Added getConsumerWsToken function to retrieve the WebSocket token from the beamcode-consumer-token meta tag.
    • Modified buildWsUrl to include the token query parameter in the WebSocket URL if a consumer WebSocket token is available.
Activity
  • The author, teng-lin, has implemented a significant security enhancement by separating API and WebSocket authentication tokens.
  • New RotatingTokenAuthority class was introduced and integrated into the application's startup process.
  • The HTTP server and WebSocket authenticator were updated to support dynamic token validation.
  • The web client's API and WebSocket usage were adjusted to accommodate the new token injection and validation mechanisms.
  • Comprehensive tests were added and verified for rotating-token-authority.test.ts, api-key-authenticator.test.ts, server.test.ts, consumer-html.test.ts, api.test.ts, and ws.test.ts.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@teng-lin teng-lin force-pushed the fix/token-scope-separation-api-ws branch from 4d96e44 to ca7bfd2 Compare February 22, 2026 20:01
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant security improvement by splitting API and WebSocket authentication tokens and implementing rotation, expiry, and revocation for them. The new RotatingTokenAuthority class is well-designed and tested. The changes are extensive, touching both server-side and client-side code, and include backward compatibility considerations. However, I've identified a high-severity issue where API calls from older clients will fail after a server update, due to the reuse of the beamcode-consumer-token meta tag for a new purpose. My review includes a detailed comment on this issue.

Comment thread web/src/api.ts
Comment on lines +7 to +10
document.querySelector<HTMLMetaElement>('meta[name="beamcode-api-token"]')?.content ??
// Backward-compatible fallback for pages that only inject the legacy meta tag.
document.querySelector<HTMLMetaElement>('meta[name="beamcode-consumer-token"]')?.content ??
null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This fallback logic correctly handles a new client working with an old server. However, the reverse scenario—an old client with a new server—appears to be broken.

An old client's JavaScript will only look for meta[name="beamcode-consumer-token"] for API authentication. The new server implementation now places the WebSocket-only token in this meta tag. Consequently, all API calls from old clients will fail with 401 Unauthorized errors after the server is updated, as they will be sending the wrong token.

To maintain backward compatibility for API calls, beamcode-consumer-token should continue to hold a token valid for the API. A new meta tag, for example <meta name="beamcode-ws-token">, should be introduced for the WebSocket token. This would require changes in src/http/consumer-html.ts to inject the tags correctly and in web/src/ws.ts to read from this new tag.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed in c981f7d: beamcode-consumer-token is API-compatible again for legacy clients, and a dedicated beamcode-ws-token now carries the scoped WS token. Updated web/src/ws.ts to prefer beamcode-ws-token with legacy fallback, and added tests for this behavior.

@teng-lin teng-lin force-pushed the fix/token-scope-separation-api-ws branch from c981f7d to f169ce8 Compare February 22, 2026 20:07
@teng-lin teng-lin merged commit a140620 into main Feb 22, 2026
6 checks passed
@teng-lin teng-lin deleted the fix/token-scope-separation-api-ws branch February 22, 2026 20:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant