Skip to content

Clarify API routing: split /api into /api/internal (cookie) vs /api/public (bearer) #38

@kentcdodds

Description

@kentcdodds

Problem

Right now our app mixes multiple auth models (cookie session vs OAuth bearer tokens), but the URL surface area doesn’t make that obvious.

In particular, /api/* is currently used for the OAuth provider apiRoute (token-protected resource APIs), while our browser-facing JSON endpoints use cookie auth on non-/api paths (ex: /session, /auth, /logout, /chat-agent). This makes it hard to reason about what belongs in “the API”, what should be CSRF-protected, and which endpoints are intended for 3rd-party / token clients vs same-origin browser clients.

Current examples:

  • OAuth protocol endpoints: /oauth/authorize, /oauth/token, /oauth/register, /oauth/callback
  • OAuth token resource endpoint: GET /api/me (handled by OAuth provider apiRoute)
  • Cookie/session endpoints: /session, /auth, /logout, /password-reset/*, /chat-agent
  • Docs mention /api/chat, but code currently routes chat via /chat-agent

Goal

Make endpoint intent + auth requirements obvious from the path so we can:

  • Avoid accidental CSRF exposure for cookie-auth JSON endpoints
  • Avoid conflating OAuth protocol endpoints with resource APIs
  • Set a clear convention for future endpoints (especially chat)

Proposed Solution

Adopt a consistent API namespace split:

  • /api/internal/* for same-origin, cookie-session-authenticated endpoints (CSRF-protected as needed)
  • /api/public/* for bearer-token-authenticated resource endpoints intended for non-browser / 3rd-party clients

Keep OAuth protocol endpoints under /oauth/* (authorize/token/register/callback).

Scope / Tasks

  • Decide final routing + naming convention (this issue)
  • Update worker routing so OAuth provider apiRoute lives at /api/public/
  • Move (or add) cookie-auth JSON endpoints under /api/internal/
  • Update any client fetches that should call internal endpoints
  • Update docs that reference /api/chat to match the chosen route (or implement /api/public/chat if that’s the intention)
  • Add e2e + unit tests covering:
    • internal endpoints require cookie session and are not accidentally bearer-authenticated
    • public endpoints require bearer tokens and do not depend on cookies

Acceptance Criteria

  • Every new endpoint clearly falls into either /api/internal/* or /api/public/*
  • /api/public/* endpoints are bearer-only (no cookie session assumptions)
  • /api/internal/* endpoints are cookie-auth only (and have CSRF posture documented/tested)
  • Documentation matches the implemented routes

References

  • worker/index.ts OAuthProvider is configured with apiRoute: '/api/' today
  • worker/oauth-handlers.ts implements GET /api/me

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions