Skip to content

feat(nextjs): add firebase-cookie-middleware with security hardening#739

Open
tyler-reitz wants to merge 9 commits into
FirebaseExtended:mainfrom
tyler-reitz:feat/nextjs-middleware
Open

feat(nextjs): add firebase-cookie-middleware with security hardening#739
tyler-reitz wants to merge 9 commits into
FirebaseExtended:mainfrom
tyler-reitz:feat/nextjs-middleware

Conversation

@tyler-reitz

Copy link
Copy Markdown
Contributor

Summary

This PR introduces firebase-cookie-middleware (v0.0.1), a production-ready Next.js middleware package for Firebase Auth cookie persistence. It takes over from #640 (original work by James Daniels, @jamesdaniels), which is being closed in favor of this PR to establish clear ownership.

All logic from #640 has been carried forward. The following security issues were identified and addressed during the takeover review:

  • Emulator explicit opt-in: FIREBASE_AUTH_EMULATOR_HOST is no longer auto-detected; requires emulator: true in config to prevent unsigned token acceptance if the env var leaks into production
  • Verify-after-refresh: refreshed ID tokens are verified before being accepted, preventing a bypass via a compromised token endpoint
  • refreshable=false on signature failure: prevents refresh attempts when the original token has an invalid signature (not just expiry)
  • SSRF: emulator host validated to be loopback-only (localhost, 127.0.0.1, ::1)
  • CSRF: Origin header checked on POST/DELETE to /__cookies__; cross-origin requests rejected with 403
  • CHIPS: SameSite=None applied correctly on partitioned cookies (was incorrectly SameSite=Lax in production)
  • JWKS eviction lock: lock released in a finally block to prevent deadlock on verification error
  • Falsy-zero TTL: ttlSeconds > 0 required before caching; 0 no longer treated as "cache forever"
  • 400 not throw: invalid proxy target hosts return 400 instead of an uncaught throw
  • Optional chaining on JWT payload: guards against undefined access on malformed tokens
  • Removed duplex: 'half': workaround was unnecessary and caused Node.js type errors

Next.js 16 compatibility: adds a proxy named export for proxy.ts (Node.js runtime). The middleware export remains for middleware.ts users (Edge Runtime, v14/15 and v16 deprecated path). Logic is identical between the two exports.

Also fixes pre-existing TypeScript strict-mode errors surfaced by upgrading moduleResolution from node to bundler.

Test plan

  • 71 unit tests pass (npm test in src/nextjs/)
  • tsc --noEmit clean at repo root and in src/nextjs/
  • Manual smoke test: emulator flow with emulator: true + FIREBASE_AUTH_EMULATOR_HOST set
  • Manual smoke test: production token verify and refresh cycle in a Next.js 15 app
  • Manual smoke test: proxy.ts export in a Next.js 16 app

Attribution

Original implementation by James Daniels (jamesdaniels@google.com), PR #640. Takeover and security hardening by Tyler Dixon.

Closes #640

@tyler-reitz tyler-reitz mentioned this pull request Jul 16, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@tyler-reitz

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

jamesdaniels and others added 6 commits July 16, 2026 15:51
- Move jose from devDependencies to dependencies (runtime dep)
- Strip API key from refresh error log (log origin+pathname only)
- Fix exp=0 falsy guard (use !== undefined)
- Release JWKS eviction lock on fetch error (add cacheDelete + try/finally)
- Return 400 for malformed finalTarget URL instead of throwing TypeError
- Fill in empty error message in composeMiddleware config guard
- Align tenantId docs to string-only (function form deferred)
- Add del() to CacheProvider interface and MemoryCacheProvider
- Annotate @firebase/util and _AuthEmulatorRefreshToken as internal/fragile
- Remove self-referential PR comment and resolved TODO
- Centralize createToken test helper in middleware_test_utils.ts
Introduces the firebase-cookie-middleware package (v0.0.1) for
Next.js 14/15/16, taking over from PR FirebaseExtended#640 (James Daniels).

Security fixes applied during takeover review:
- Emulator explicit opt-in only (env var not auto-detected)
- JWT signature failure sets refreshable=false to prevent bypass
- Verify refreshed token before accepting it
- SSRF: loopback validation on emulator host
- CSRF: Origin check on POST/DELETE to /__cookies__
- CHIPS: SameSite=None on partitioned cookies
- JWKS eviction lock released in finally block
- Falsy-zero TTL: require ttlSeconds > 0 before caching
- Proxy targets validated; return 400 instead of throwing
- Optional chaining on JWT payload access
- Removed duplex: 'half' workaround (unnecessary in Node.js)

Next.js 16: adds proxy export (proxy.ts replaces middleware.ts
in Next.js 16 projects). proxy.ts runs on Node.js runtime only;
middleware.ts remains available for Edge Runtime (deprecated).

Also fixes pre-existing TypeScript strict-mode errors surfaced
by upgrading moduleResolution from node to bundler.

Ref: FirebaseExtended#640
Original work by: James Daniels (jamesdaniels@google.com)
@tyler-reitz
tyler-reitz force-pushed the feat/nextjs-middleware branch from 40b253c to 37050fa Compare July 16, 2026 22:51
@tyler-reitz

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

The nextjs package is a self-contained sub-package with its own
tsconfig and test runner. Including it in the root tsc/vitest runs
causes failures because next is not installed as a root dependency.
Use **/ prefix so the patterns match at any directory depth,
covering both src/nextjs/ and package/src/nextjs/ (the CI test
job unpacks the npm tarball alongside the checkout, creating both
paths). Also broaden node_modules exclusion to catch nested
directories like functions/node_modules/.
@tyler-reitz

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@tyler-reitz

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

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.

2 participants