Skip to content

feat(server): cookie lifecycle & CSRF enforcement (#749)#1117

Merged
pyramation merged 9 commits into
mainfrom
feat/cookie-lifecycle-csrf-749
May 12, 2026
Merged

feat(server): cookie lifecycle & CSRF enforcement (#749)#1117
pyramation merged 9 commits into
mainfrom
feat/cookie-lifecycle-csrf-749

Conversation

@theothersideofgod
Copy link
Copy Markdown
Contributor

@theothersideofgod theothersideofgod commented May 11, 2026

Summary

Implement complete cookie authentication lifecycle and CSRF protection (Issue #749 — Phase 1 of #735)

  • CSRF Middleware — Double-Submit Cookie pattern protection
  • Session Cookie — Set on sign-in, clear on sign-out
  • Device Token — 90-day device token support
  • Remember Me — Configurable extended session duration

Architecture

Request Flow:
┌─────────┐    ┌──────┐    ┌──────┐    ┌───────┐    ┌──────┐    ┌─────────┐
│  CORS   │───▶│  API │───▶│ Auth │───▶│Captcha│───▶│ CSRF │───▶│ Graphile│
└─────────┘    └──────┘    └──────┘    └───────┘    └──────┘    └─────────┘
                                                        │              │
                                          csrf_token ◀──┘   AuthCookie │
                                          cookie set       Plugin sets │
                                                           session     ▼
                                                           cookie    Response

CSRF Protection

Request Type CSRF Check Reason
Bearer token Skip Not vulnerable to CSRF
Anonymous Skip No session to exploit
Cookie auth Enforce Browser auto-sends cookies
# Verify CSRF protection
curl -X POST http://api.localhost:3000/graphql \
  -H "Cookie: constructive_session=TOKEN; csrf_token=ABC" \
  -d '{"query":"{ currentUserId }"}'
# → 403 CSRF token validation failed

curl -X POST http://api.localhost:3000/graphql \
  -H "Cookie: constructive_session=TOKEN; csrf_token=ABC" \
  -H "X-CSRF-Token: ABC" \
  -d '{"query":"{ currentUserId }"}'
# → 200 OK

Commits

# Commit Description Tests
1 7f9b7a4 Cookie utility module 279 lines
2 960efce CSRF middleware integration 170 lines
3 2f1f37b Add rememberMeDuration to AuthSettings
4 d133c29 AuthCookiePlugin (core) 1098 lines
5 e393e6b Wire into middleware chain
6 b1a0c73 Return 403 for CSRF errors
7 b1190bc Fix cookie setting
8 c3310d2 Device token reading
9 9b0e35f Device token tests 192 lines

Total test coverage: ~1700 lines

Files Changed

Core Implementation

File Purpose
middleware/cookie.ts Cookie serialization & config utilities
plugins/auth-cookie-plugin.ts Grafserv plugin to intercept auth mutations
server.ts Middleware chain configuration

Integration

File Purpose
middleware/auth.ts Read device token cookie
middleware/graphile.ts Pass device token to DB context
middleware/api.ts Load remember_me_duration setting
middleware/error-handler.ts Return 403 for CSRF errors

Dependencies

+ @constructive-io/csrf
+ cookie-parser
+ @types/cookie-parser

How to Test

# 1. Anonymous sign up (CSRF skipped)
curl -X POST http://api.localhost:3000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { signUp(input: {email: \"test@example.com\", password: \"Test123!\"}) { result { accessToken } } }"}'

# 2. Cookie auth without CSRF (should fail 403)
curl -X POST http://api.localhost:3000/graphql \
  -H "Cookie: constructive_session=<token>; csrf_token=abc" \
  -d '{"query":"{ currentUserId }"}'

# 3. Cookie auth with CSRF (should succeed 200)
curl -X POST http://api.localhost:3000/graphql \
  -H "Cookie: constructive_session=<token>; csrf_token=abc" \
  -H "X-CSRF-Token: abc" \
  -d '{"query":"{ currentUserId }"}'

Not Included (Future Work)

  • Anonymous session creation for DB-level CSRF validation
  • Cookie auth flow end-to-end (DB sign_in expects session)

Closes #749

🤖 Generated with Claude Code

@theothersideofgod theothersideofgod force-pushed the feat/cookie-lifecycle-csrf-749 branch 2 times, most recently from fa376a0 to e5e066b Compare May 11, 2026 07:38
theothersideofgod and others added 9 commits May 11, 2026 18:44
- Add SESSION_COOKIE_NAME and DEVICE_TOKEN_COOKIE_NAME constants
- Add getSessionCookieConfig() with rememberMe support
- Add getDeviceTokenCookieConfig() for 90-day device tokens
- Add cookie serialization helpers (set/clear)
- Add parseCookieValue() and request token extractors

Closes #749

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add @constructive-io/csrf dependency
- Wire csrfSetToken middleware (httpOnly=false for SPA access)
- Wire csrfProtect middleware on /graphql endpoint
- Skip CSRF for Bearer token auth (not vulnerable)
- Skip CSRF for anonymous requests (no session cookie)
- Add integration tests for CSRF skip conditions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add rememberMeDuration field to AuthSettings interface
- Query remember_me_duration from app_settings_auth table
- Used by cookie config when rememberMe=true in sign-in

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Implement grafserv middleware plugin to set/clear auth cookies
- Intercept signIn/signUp/SSO/MFA mutations to set session cookie
- Intercept signOut/revokeSession to clear cookies
- Handle device token cookies for trusted device tracking
- Parse grafserv BufferResult and inject Set-Cookie headers
- Support both camelCase and snake_case token fields
- Support nested result objects

Includes comprehensive tests:
- Auth failure scenarios (errors, null data, invalid token types)
- Cookie clearing completeness (session + device token)
- Environment-based security attributes
- Grafserv Buffer parsing and header merging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AuthCookiePlugin to graphile preset plugins array
- Remove Express middleware approach (doesn't work with grafserv)
- Add CSRF middleware after authenticate, before graphile
- Update server.ts middleware order

Middleware chain:
cors → api → authenticate → captcha → csrf → graphile (with AuthCookiePlugin)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add CSRF error detection to error handler so CSRF validation failures
return proper 403 Forbidden status instead of generic 500.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cookie-parser middleware to support CSRF double-submit pattern
- Parse GraphQL body from grafserv's getBody() buffer in AuthCookiePlugin
- Set cookies directly on Express response to ensure proper HTTP headers
- Fix NaN maxAge by handling unparseable authSettings values

The AuthCookiePlugin now correctly intercepts auth mutations and sets
session cookies via the Express response, ensuring multiple Set-Cookie
headers are sent separately as required by HTTP spec.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Read constructive_device_token cookie in auth middleware
- Attach to req.deviceToken for downstream access
- Pass as jwt.claims.device_token to DB procedures
- Enables trusted device recognition in sign-in flows

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test device token cookie parsing in auth middleware
- Test device token context passing in graphile preset
- Covers edge cases: missing cookie, URL encoding, special chars

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@theothersideofgod theothersideofgod force-pushed the feat/cookie-lifecycle-csrf-749 branch from 3f7ab3d to 9b0e35f Compare May 11, 2026 10:45
@pyramation pyramation merged commit a8c2647 into main May 12, 2026
54 checks passed
@pyramation pyramation deleted the feat/cookie-lifecycle-csrf-749 branch May 12, 2026 22:41
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