# How to Handle Authentication Session Cookies Between Next.js and Cloudflare Workers

> Learn to handle authentication session cookies between Next.js and Cloudflare Workers seamlessly. Utilize better-auth and nextCookies for secure, shared session tokens without custom headers.

- Repository: [Muhammad Arifin/fullstack-next-cloudflare](https://github.com/ifindev/fullstack-next-cloudflare)
- Tags: how-to-guide
- Published: 2026-03-04

---

**The `ifindev/fullstack-next-cloudflare` repository uses the `better-auth` library with the `nextCookies` plugin to store sessions in HTTP-only cookies, making the same authentication token automatically available to both Next.js runtime handlers and Cloudflare Worker edge middleware without custom header forwarding.**

When running Next.js on Cloudflare Workers, the edge runtime executes your application code before requests reach the page router. This creates a challenge: how do you validate user sessions in the middleware layer while maintaining access to that same session inside your API routes and Server Actions? The solution lies in leveraging standard HTTP cookie semantics that work identically across both execution contexts.

## The Architecture Overview

The architecture centers on **better-auth** configured with the **nextCookies** plugin. This combination stores the user session in a standard HTTP-only cookie that browsers automatically attach to every request. Because Cloudflare Workers execute the Next.js middleware before the request hits your application code, and because both environments read from the same `Cookie` header, you get seamless session sharing without duplicated token stores or complex synchronization logic.

### How the nextCookies Plugin Enables Cross-Runtime Access

In [`src/modules/auth/utils/auth-utils.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-utils.ts), the auth instance is configured with the `nextCookies()` plugin:

```typescript
// src/modules/auth/utils/auth-utils.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";

let cachedAuth: ReturnType<typeof betterAuth> | null = null;

async function getAuth() {
  if (cachedAuth) return cachedAuth;

  const { env } = await getCloudflareContext();
  const db = await getDb();

  cachedAuth = betterAuth({
    secret: env.BETTER_AUTH_SECRET,
    database: drizzleAdapter(db, { provider: "sqlite" }),
    emailAndPassword: { enabled: true },
    socialProviders: {
      google: {
        enabled: true,
        clientId: env.GOOGLE_CLIENT_ID!,
        clientSecret: env.GOOGLE_CLIENT_SECRET!,
      },
    },
    // ← This plugin makes the session cookie work on both Next.js and Workers
    plugins: [nextCookies()],
  });

  return cachedAuth;
}

```

The `nextCookies` plugin handles the underlying cookie serialization and deserialization, ensuring that when `auth.api.getSession({ headers })` is called in any context—whether inside a React Server Component, a Server Action, or the edge middleware—it reads from the same cookie store.

### The Five-Step Authentication Flow

1. **Login initiation**: The client calls `/api/auth/...` routes generated by `better-auth/next-js`. The server creates a cryptographically signed session cookie and returns it via `Set-Cookie` headers.

2. **Automatic propagation**: The browser stores the HTTP-only cookie and attaches it to every subsequent request, regardless of whether the target is a Next.js page, a Server Action, or the Worker middleware.

3. **Server-side validation**: Application code uses helpers from [`src/modules/auth/utils/auth-utils.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-utils.ts) to retrieve the current user:
   - `getCurrentUser()` returns the user payload or `null` by calling `auth.api.getSession({ headers })`.
   - `requireAuth()` throws an error if no valid session exists, ideal for protected data fetching.
   - `isAuthenticated()` returns a boolean flag for conditional rendering logic.

4. **Edge protection**: [`src/middleware.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/middleware.ts) runs before the request reaches the page router. It initializes the same auth singleton via `getAuthInstance()` and validates the session using `auth.api.getSession({ headers: request.headers })`. Missing sessions trigger an immediate redirect to `/login`.

5. **Client-side consistency**: The browser-side client in [`src/modules/auth/utils/auth-client.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-client.ts) points to the same `/api/auth/...` endpoints. OAuth flows and password sign-ins automatically set the cookie that the Worker later validates.

## Implementing the Shared Session Cookie

### Creating the Central Auth Instance

The singleton pattern in [`src/modules/auth/utils/auth-utils.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-utils.ts) ensures that both the middleware and your application routes use the same configuration and database connection. This file exports `getAuthInstance()` (aliased as `getAuth` in some contexts) which caches the auth object between requests in the same Worker instance.

### Protecting Routes at the Edge

The [`src/middleware.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/middleware.ts) file demonstrates how to intercept requests before they consume application resources:

```typescript
// src/middleware.ts
import { getAuthInstance as getAuth } from "@/modules/auth/utils/auth-utils";
import { NextResponse } from "next/server";

export async function middleware(request: Request) {
  const auth = await getAuth();
  const session = await auth.api.getSession({ headers: request.headers });

  if (!session) {
    // No cookie → redirect to login page before the request reaches Next.js
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

```

This middleware efficiently validates the session cookie at the network edge, preventing unauthenticated requests from ever hitting your database or rendering logic.

### Accessing Sessions in Server Components and Actions

For protected data fetching, use the `requireAuth()` helper inside Server Actions:

```typescript
// Example: src/modules/todos/actions/get-todos.action.ts
import { requireAuth } from "@/modules/auth/utils/auth-utils";

export async function getTodos() {
  const user = await requireAuth();   // throws if no valid cookie
  // `user` now contains { id, name, email }
  // … fetch data for this user …
}

```

This pattern ensures that any attempt to access protected resources without a valid session immediately fails with an authentication error.

### Setting Up the Dynamic API Handler

The auth routes must be exposed through a catch-all API handler in `src/app/api/auth/[...all]/route.ts`:

```typescript
// src/app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { getAuthInstance } from "@/modules/auth/utils/auth-utils";

const createHandler = async () => {
  const auth = await getAuthInstance();
  return toNextJsHandler(auth.handler);
};

export async function GET(request: Request) {
  const { GET: handler } = await createHandler();
  return handler(request);
}
export async function POST(request: Request) {
  const { POST: handler } = await createHandler();
  return handler(request);
}

```

This dynamic route forwards all authentication-related requests (login, logout, callbacks) to the better-auth handler, which manages the session cookie lifecycle.

### Client-Side Integration

On the browser, initiate sign-in using the client from [`src/modules/auth/utils/auth-client.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-client.ts):

```tsx
// src/modules/auth/components/login-form.tsx
import { authClient } from "@/modules/auth/utils/auth-client";

const signInWithGoogle = async () => {
  await authClient.signIn.social({
    provider: "google",
    // After a successful OAuth flow, the server sets the session cookie
    callbackURL: "/dashboard",
  });
};

```

Once the OAuth flow completes, the server sets the HTTP-only cookie, and subsequent navigation to `/dashboard` passes through the middleware validation successfully.

## Summary

- **Use the `nextCookies` plugin** from better-auth to store sessions in standard HTTP-only cookies readable by both Next.js and Cloudflare Workers.
- **Implement a singleton auth instance** in [`src/modules/auth/utils/auth-utils.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-utils.ts) to share configuration between middleware and application code.
- **Validate at the edge** in [`src/middleware.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/middleware.ts) using `auth.api.getSession({ headers: request.headers })` to protect routes before they reach your application.
- **Leverage helper functions** like `requireAuth()` and `getCurrentUser()` for type-safe session access inside Server Components and Actions.
- **Maintain consistency** by using the same auth client on the browser and the same API routes for all authentication flows.

## Frequently Asked Questions

### How does the `nextCookies` plugin enable session sharing between Next.js and Workers?

The `nextCookies` plugin serializes the user session into a standard HTTP-only cookie that conforms to browser specifications. Because Cloudflare Workers execute the Next.js middleware in the same request context as the application runtime, both environments read from the identical `Cookie` header. This eliminates the need for separate token stores or custom header injection between the edge and the application server.

### Can I use this authentication pattern with other providers besides Google?

Yes. The configuration in [`src/modules/auth/utils/auth-utils.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/modules/auth/utils/auth-utils.ts) supports multiple social providers through the `socialProviders` object, and the `emailAndPassword` option enables credential-based authentication. The session cookie mechanism works identically regardless of the authentication method used, as the `nextCookies` plugin handles cookie operations independently of the provider strategy.

### How does the middleware protect routes before they reach the Next.js router?

The [`src/middleware.ts`](https://github.com/ifindev/fullstack-next-cloudflare/blob/main/src/middleware.ts) file executes at the Cloudflare Worker edge before the request enters the Next.js routing layer. By calling `auth.api.getSession({ headers: request.headers })`, it inspects the incoming cookie and returns a `NextResponse.redirect()` to `/login` if validation fails. This prevents unauthenticated requests from consuming compute resources or accessing database connections in your application code.

### Is the session cookie secure for production deployments?

The implementation uses HTTP-only cookies, which prevent JavaScript access on the client side, reducing XSS attack vectors. The cookies are cryptographically signed using the `BETTER_AUTH_SECRET` environment variable. For production, ensure you configure the `secure` attribute (automatically handled by better-auth in HTTPS environments) and set appropriate `SameSite` policies based on your domain architecture.