Skip to content

Fix client bundling and upgrade authkit-session to 0.3.0#26

Merged
nicknisi merged 24 commits intomainfrom
nicknisi/session-refresh
Dec 10, 2025
Merged

Fix client bundling and upgrade authkit-session to 0.3.0#26
nicknisi merged 24 commits intomainfrom
nicknisi/session-refresh

Conversation

@nicknisi
Copy link
Member

@nicknisi nicknisi commented Dec 3, 2025

Summary

This PR upgrades @workos/authkit-session to 0.3.0 and fixes critical session persistence issues.

Problem: Can't Use TanStack's Built-in setResponseHeaders

TanStack Start provides setResponseHeaders() for setting cookies from server functions. However, importing it breaks builds:

"Readable" is not exported by "__vite-browser-external"

This happens because TanStack's barrel exports pull in node:stream code that Vite can't handle. This is a known issue (TanStack/router#4022).

Impact: When refreshSession() or switchToOrganization() is called, the session cookie can't be persisted. Users switch orgs successfully, but on page refresh they revert to their previous org.

Solution: Context-Based Session Persistence

Instead of importing from @tanstack/react-start/server, we use middleware context to defer cookie persistence:

1. Middleware creates context with `__setPendingHeader` setter
2. Server function refreshes session → gets encryptedSession
3. Server function stores pending header via context setter
4. Middleware reads pending headers after args.next() completes
5. Middleware applies cookies to the response

This follows the standard middleware pattern used in Express/Koa/etc.

Additional Fixes

Changes

  • storage.ts - No TanStack server imports; uses context for headers
  • middleware.ts - Passes header setter through context, applies pending headers to response
  • auth-helpers.ts - Returns session data for middleware to persist
  • context.ts - Try-catch for graceful context access
  • server-functions.ts / actions.ts - Use context-based persistence

Related

@nicknisi nicknisi changed the title Upgrade authkit-session to 0.2.0 with session refresh support Fix client bundling and upgrade authkit-session to 0.2.0 Dec 3, 2025
@nicknisi nicknisi marked this pull request as ready for review December 3, 2025 17:06
@greptile-apps
Copy link

greptile-apps bot commented Dec 3, 2025

Greptile Overview

Greptile Summary

This PR upgrades @workos/authkit-session to 0.3.0 and implements a comprehensive solution for session persistence and client bundling issues.

Key Changes:

Architecture:
The session persistence now flows through middleware context: server functions store pending headers via __setPendingHeader, and middleware applies them to the response after args.next() completes. This pattern ensures cookies are properly set even when TanStack's context becomes unavailable post-handler.

Testing:
Comprehensive test coverage added for all new modules including middleware header merging, context availability, auth helpers, and client-side organization switching.

Confidence Score: 5/5

Important Files Changed

File Analysis

Filename Score Overview
src/server/middleware.ts 5/5 Refactored to use lazy-loaded authkit and context-based header persistence. Properly handles multiple Set-Cookie headers via append, preserves existing response headers, and applies refreshed session cookies after args.next().
src/server/context.ts 5/5 New file providing context utilities for accessing middleware-set auth state. getAuthKitContextOrNull gracefully handles unavailable context with try-catch.
src/server/storage.ts 5/5 Updated to use context-based header persistence when available, with fallback to response headers when context is unavailable (e.g., after args.next() in middleware).
src/server/authkit-loader.ts 5/5 New lazy-loading orchestrator for AuthKit service. Uses singleton pattern for authkit instance. Replaces static import to prevent server deps from leaking into client bundles.
src/server/auth-helpers.ts 5/5 New helper module consolidating auth operations. Contains getRawAuthFromContext, isAuthConfigured, getSessionWithRefreshToken, refreshSession, and decodeState utilities.
src/server/actions.ts 5/5 Refactored to use auth-helpers module. Added sanitizeAuthForClient to strip accessToken before sending to client. Improved error handling with isAuthConfigured checks.
src/server/server-functions.ts 5/5 Updated to use lazy-loaded getAuthkit() and consolidated auth-helpers. signOut now uses authkit.signOut() for proper session cleanup. switchToOrganization delegates to refreshSession helper.
src/server/server.ts 5/5 Simplified OAuth callback handler using consolidated decodeState helper. extractSessionHeaders function handles both response.headers and direct headers object patterns.
src/index.ts 5/5 Simplified to re-export types and server/index.ts. Removes explicit exports in favor of barrel export pattern.
package.json 5/5 Updated @workos/authkit-session to 0.3.0 and upgraded dev dependencies including TanStack packages to 1.140.0.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Middleware
    participant ServerFunction
    participant AuthKit
    participant Storage

    Client->>Middleware: Request
    Middleware->>AuthKit: getAuthkit()
    AuthKit-->>Middleware: authkit instance
    Middleware->>AuthKit: withAuth(request)
    AuthKit-->>Middleware: { auth, refreshedSessionData }
    
    Note over Middleware: Create pendingHeaders & context with __setPendingHeader
    
    Middleware->>ServerFunction: args.next({ context })
    
    alt Server function refreshes session
        ServerFunction->>AuthKit: refreshSession(orgId)
        AuthKit->>Storage: saveSession()
        Storage->>Storage: getAuthKitContextOrNull()
        alt Context available
            Storage->>Middleware: __setPendingHeader(Set-Cookie, value)
        else Context unavailable
            Storage-->>ServerFunction: { response with headers }
        end
    end
    
    ServerFunction-->>Middleware: result
    
    alt refreshedSessionData exists
        Middleware->>AuthKit: saveSession(undefined, refreshedSessionData)
        AuthKit-->>Middleware: { response with Set-Cookie }
        Middleware->>Middleware: Extract & append Set-Cookie to pendingHeaders
    end
    
    Middleware->>Middleware: Apply pendingHeaders to response
    Middleware-->>Client: Response with session cookies
Loading

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@nicknisi nicknisi changed the title Fix client bundling and upgrade authkit-session to 0.2.0 Fix client bundling and upgrade authkit-session to 0.3.0 Dec 8, 2025
@briggsrrr
Copy link

🙏

@nicknisi
Copy link
Member Author

@greptileai

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

28 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

- Create src/server/context.ts with AuthKitServerContext interface
- Add getAuthKitContext() and getAuthKitContextOrNull() helpers
- Remove `as any` casts from production code
- Rename _setPendingHeader to __setPendingHeader (deeper internal signal)
- Use Headers API instead of Record<string,string> for pendingHeaders
  to properly handle multiple Set-Cookie values via .append()
- Add runtime validation for both auth and request in context
- Convert error message to template literal for readability
- Add unit tests for context.ts (12 tests) and middleware.ts (7 tests)
- Fix test mock to include request property
- Standardize TanStack dependency versioning in example
TanStack Start's AsyncLocalStorage context is torn down after args.next()
returns. This fixes session cookie persistence for the refresh case by:

- Storage falls back to putting headers on response when context unavailable
- Middleware extracts Set-Cookie from saveSession response and adds to pendingHeaders
- Updated tests to cover both context-available and fallback paths
Dynamic imports were originally used to avoid bundling server-only code
into the client, but with the src/server/ directory boundary working
properly, static imports are sufficient and simpler.
@nicknisi nicknisi force-pushed the nicknisi/session-refresh branch from 85fbc82 to 3c43334 Compare December 10, 2025 19:12
@nicknisi nicknisi merged commit 8fd98a0 into main Dec 10, 2025
6 checks passed
@nicknisi nicknisi mentioned this pull request Dec 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

@workos-inc/node is being loaded in browser causing "pluralize module not found" error with Convex integration

3 participants