Skip to content

API Gateway 401/403 responses missing CORS headers (browser sees CORS error instead of auth error) #25

@sdobnaya

Description

@sdobnaya

Context:
EasySAM generates AWS SAM templates for API Gateway (AWS::Serverless::Api), including routes, authorizers, and CORS configuration.

Problem:
When a request fails authentication (invalid or missing token), API Gateway returns a 401 UnauthorizedException BEFORE invoking Lambda.

These 401/403 responses do NOT include CORS headers, even though:

  • OPTIONS (preflight) responses include correct CORS headers
  • Successful authenticated requests include correct CORS headers

Observed behavior:

  • OPTIONS → 200 with CORS headers
  • GET with valid token → 200, response readable in browser
  • GET with invalid/missing token → 401 UnauthorizedException (from API Gateway)
  • 401 response has NO Access-Control-Allow-Origin header
  • Browser blocks response and reports CORS/network error instead of exposing 401

Impact:

  • Frontend cannot distinguish auth failure from network/CORS failure
  • Proper auth flows (token refresh, re-login) break
  • Debugging becomes misleading

Root cause:
EasySAM configures CORS only for:

  • OPTIONS (preflight)
  • Integration/Lambda responses (via Swagger/SAM)

But does NOT configure API Gateway GatewayResponses:

  • UNAUTHORIZED
  • ACCESS_DENIED
  • DEFAULT_4XX
  • DEFAULT_5XX

Since these responses are generated by API Gateway before Lambda, runtime code (FastAPI/Vial/etc.) cannot add CORS headers.

Why this belongs in EasySAM:
EasySAM owns API Gateway/SAM generation (RestApi, Authorizers, Swagger, CORS).
The missing behavior is at API Gateway configuration level, not application code.

Proposed fix:
Extend EasySAM template generation to include GatewayResponses with CORS headers for every API that has CORS enabled.

Implementation requirements:

  1. For each generated RestApi, add resources:

    • AWS::ApiGateway::GatewayResponse:
      • DEFAULT_4XX
      • DEFAULT_5XX
      • UNAUTHORIZED
      • ACCESS_DENIED
  2. Each GatewayResponse must include:
    gatewayresponse.header.Access-Control-Allow-Origin
    gatewayresponse.header.Access-Control-Allow-Methods
    gatewayresponse.header.Access-Control-Allow-Headers
    (and Access-Control-Allow-Credentials if CORS config enables it)

  3. Header values must:

    • Reuse existing CORS configuration from EasySAM (origins, headers, credentials)
    • NOT be hardcoded
    • Match behavior of successful responses
  4. Solution must be:

    • Global (API-level), not per-route
    • Path-agnostic
    • Compatible with authorizer-generated responses (pre-Lambda)
    • Automatically applied (no manual patching in services)
  5. Do NOT:

    • Modify Lambda runtime code
    • Add per-route CORS hacks
    • Depend on specific endpoints like /assets or /channels

Acceptance criteria:

  • GET with invalid/missing token returns 401 visible to browser (not CORS error)
  • 401 response includes Access-Control-Allow-Origin
  • Frontend can read response and handle auth failure correctly
  • Behavior consistent across all routes behind the same API Gateway
  • OPTIONS and successful requests remain unchanged

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions