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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions