80% off for waitlist membersJoin now and lock in Launch from $39.80 or Lifetime from $49.80 

← Back to Guides

WooCommerce REST API + Next.js: Authentication, Sessions and Cart Handling

WPBundle Team··13 min read
woocommerce rest api nextjswoocommerce nextjs tutorialnextjs woocommercewoocommerce api authentication

Connecting Next.js to the WooCommerce REST API sounds straightforward — fetch some JSON, render some products, done. In practice, the authentication layer, session management, and cart persistence will consume more of your time than the actual product pages. This guide covers the specific implementation details that other tutorials gloss over: how authentication actually works across different contexts, how to keep a cart alive across page navigations in a stateless frontend, and how to avoid the pitfalls that trip up every developer building their first headless WooCommerce store.

TL;DR

Use consumer keys for server-side API calls (Next.js Server Components and Route Handlers). Use the WooCommerce Store API for cart and checkout — it handles sessions via a Cart-Token header. Never expose consumer keys to the browser. Store all credentials in environment variables. For cart persistence, store the cart token in a cookie and pass it with every Store API request. JWT and Application Passwords are options for customer-authenticated flows but add complexity.

Authentication methods explained

WooCommerce offers several authentication mechanisms. Each suits a different context. Choosing the wrong one — or mixing them incorrectly — is the most common source of bugs in headless WooCommerce builds.

3

Authentication methods available

0

Should be exposed to the browser

100%

Must use HTTPS in production

Consumer keys (OAuth 1.0a)

This is the standard WooCommerce authentication method. You generate a Consumer Key and Consumer Secret in WooCommerce → Settings → Advanced → REST API. These credentials authenticate requests to the /wp-json/wc/v3/ endpoints — products, orders, customers, coupons, and everything else in the WooCommerce data model.

Over HTTPS (which you must use in production), you pass them as query parameters or in the request body: ?consumer_key=ck_xxx&consumer_secret=cs_xxx. Over HTTP (local development only), WooCommerce requires full OAuth 1.0a signature generation. Libraries like @woocommerce/woocommerce-rest-api handle this automatically.

Never expose consumer keys to the client

Consumer keys grant full read/write access to your store data. If they leak to the browser (via client-side fetch calls, bundled JavaScript, or network inspection), anyone can read your customer list, modify orders, or delete products. All WooCommerce REST API calls using consumer keys must happen server-side — in Next.js Server Components, Route Handlers, or Server Actions.

Application Passwords

WordPress 5.6 introduced Application Passwords — per-user credentials that authenticate via HTTP Basic Auth. You generate them in Users → Profile → Application Passwords. They work with the WooCommerce REST API and are useful when you need requests to run in the context of a specific WordPress user (e.g., a customer viewing their own orders).

The header format is Authorization: Basic base64(username:application_password). Application Passwords respect WordPress user roles and capabilities, so a customer-level password cannot access admin-only endpoints. This makes them suitable for authenticated customer flows — order history, saved addresses, account details — without granting full store access.

JWT (JSON Web Tokens)

JWT authentication is not built into WordPress or WooCommerce. You need a plugin — typically JWT Authentication for WP REST API or Simple JWT Login. The flow is: POST credentials to a token endpoint, receive a signed JWT, then include it as a Bearer token in subsequent requests.

JWTs are stateless (no server-side session lookup on every request), which makes them attractive for headless architectures. However, they add a plugin dependency, require token refresh logic, and introduce another attack surface. For most headless WooCommerce stores, consumer keys for server-side calls plus the Store API for cart operations covers everything without JWT complexity.

Pros

  • Consumer keys: zero setup, built into WooCommerce, battle-tested
  • Application Passwords: per-user scoping, no plugins required
  • JWT: stateless, works well for mobile apps and SPAs
  • All three work with the standard WooCommerce REST API endpoints

Cons

  • Consumer keys: full store access — must never reach the browser
  • Application Passwords: require HTTPS, one per user, no fine-grained scopes
  • JWT: requires a plugin, token refresh logic, and careful secret management
  • None of these solve the cart session problem (that needs the Store API)

Environment variable setup

Your Next.js project needs several environment variables to communicate with WooCommerce. Create a .env.local file in your project root. Next.js automatically loads this file and keeps variables without the NEXT_PUBLIC_ prefix server-side only.

# .env.local
WOOCOMMERCE_URL=https://your-store.com
WOOCOMMERCE_CONSUMER_KEY=ck_your_consumer_key_here
WOOCOMMERCE_CONSUMER_SECRET=cs_your_consumer_secret_here

# Only if using JWT authentication
WC_JWT_SECRET=your_jwt_secret_here

The critical rule: never prefix secrets with NEXT_PUBLIC_. That prefix tells Next.js to bundle the variable into client-side JavaScript, making it visible to anyone who opens browser DevTools. Your WooCommerce URL can be public (it's discoverable anyway), but keys and secrets must stay server-side.

Vercel and environment variables

If you deploy on Vercel, add these variables in the project dashboard under Settings → Environment Variables. Do not commit .env.local to Git. Add it to your .gitignore if it is not already there — the Next.js default .gitignore includes it, but verify.

Server Components vs Client Components for API calls

Next.js App Router distinguishes between Server Components (the default) and Client Components (marked with "use client"). This distinction is critical for WooCommerce API calls because it determines where your code executes and what credentials are available.

Server Components: product data and catalogue

Server Components run exclusively on the server. They can access environment variables directly, make authenticated API calls with consumer keys, and return rendered HTML to the browser. Use them for anything that reads WooCommerce data: product listings, category pages, single product pages, and content from the REST API or WPGraphQL.

// app/products/page.jsx (Server Component — no "use client")
async function getProducts() {
  const res = await fetch(
    `${process.env.WOOCOMMERCE_URL}/wp-json/wc/v3/products?per_page=20` +
    `&consumer_key=${process.env.WOOCOMMERCE_CONSUMER_KEY}` +
    `&consumer_secret=${process.env.WOOCOMMERCE_CONSUMER_SECRET}`,
    { next: { revalidate: 300 } } // ISR: revalidate every 5 minutes
  )
  if (!res.ok) throw new Error('Failed to fetch products')
  return res.json()
}

export default async function ProductsPage() {
  const products = await getProducts()
  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  )
}

The next: { revalidate: 300 } option enables Incremental Static Regeneration. The page is statically generated at build time and revalidated every five minutes. This gives you the performance of static pages with near-real-time product data — a pattern that works well for catalogues with moderate update frequency.

Client Components: cart interactions

Client Components run in the browser. They handle user interactions — adding items to a cart, updating quantities, applying coupons. These components cannot access server-side environment variables. Instead, they call the WooCommerce Store API (which uses a different authentication mechanism) or proxy through Next.js Route Handlers.

The cart session problem

This is where most headless WooCommerce tutorials fall apart. WooCommerce's cart was designed for server-rendered PHP pages where PHP sessions and cookies handle state automatically. In a headless setup, there is no PHP session. Every API request is stateless. If you add an item to the cart via the REST API and then navigate to another page, the cart is gone.

The core /wc/v3/ REST API does not have cart endpoints at all. It handles products, orders, and customers — but not the shopping cart. This is not an oversight; it reflects WooCommerce's architecture. The cart is a session-based construct, not a persistent data model.

0

Cart endpoints in the WC REST API v3

3

Main approaches to solve this

#1

Pain point in headless WooCommerce

Approach 1: WooCommerce Store API (recommended)

WooCommerce ships a separate API specifically for storefront operations: the Store API, available at /wp-json/wc/store/v1/. It includes endpoints for cart operations (/cart, /cart/add-item, /cart/remove-item, /cart/update-item), checkout, and product collection queries optimised for frontend use.

The Store API handles sessions via a Cart-Token nonce header. On the first request, WooCommerce returns a nonce in the response headers. You store this token (typically in a cookie) and send it with every subsequent Store API request. As long as the token is present, WooCommerce maintains the same cart session.

// lib/store-api.js
export async function addToCart(productId, quantity = 1) {
  const cartToken = getCartTokenFromCookie()

  const headers = {
    'Content-Type': 'application/json',
  }
  if (cartToken) {
    headers['Cart-Token'] = cartToken
  }

  const res = await fetch(
    `${process.env.NEXT_PUBLIC_WOOCOMMERCE_URL}/wp-json/wc/store/v1/cart/add-item`,
    {
      method: 'POST',
      headers,
      body: JSON.stringify({ id: productId, quantity }),
    }
  )

  // Store the nonce for subsequent requests
  const nonce = res.headers.get('Nonce')
  if (nonce) saveCartTokenToCookie(nonce)

  return res.json()
}

Store API authentication

The Store API does not use consumer keys. It is designed for unauthenticated storefront access — the same way a regular WooCommerce frontend works without logging in. The nonce/token mechanism ties requests to a session, not a user. This is safe because the Store API only exposes storefront-appropriate data (cart contents, public product info, checkout) — not admin data.

Approach 2: CoCart plugin

CoCart is a dedicated REST API plugin for headless cart management. It adds proper RESTful cart endpoints with session handling built in. CoCart generates a unique cart key that persists across requests, solving the session problem with a cleaner API than WooCommerce's native Store API.

The trade-off is a plugin dependency. CoCart is well-maintained and widely used in headless WooCommerce builds, but it is another moving part in your stack. If you want to minimise WordPress plugin dependencies, the native Store API is sufficient. If you want a cleaner developer experience for cart operations, CoCart is worth evaluating.

Approach 3: Custom session management

Some teams build their own cart layer entirely outside WooCommerce — storing cart state in a database (Redis, Supabase, or even localStorage) and only syncing with WooCommerce at checkout by creating an order via the REST API. This gives you full control but means reimplementing pricing rules, tax calculations, coupon validation, and shipping logic that WooCommerce handles natively. Unless you have specific requirements that the Store API cannot satisfy, this approach creates more problems than it solves.

Persistent cart across page navigations

A cart that empties when the user navigates to a different page is not a cart — it is a bug. In a headless Next.js storefront, cart persistence requires deliberate implementation. Here is the pattern that works reliably.

  • Store the cart session token in an HTTP-only cookie (not localStorage)
  • Use a React Context provider to hold cart state globally
  • Fetch cart contents on initial page load using the stored token
  • Update cart state optimistically on user actions (add, remove, update)
  • Sync with the Store API in the background after each mutation
  • Handle token expiry gracefully — create a new session if the token is stale

The cookie-based approach survives page refreshes, browser restarts, and even device switches if the customer is logged in. Set the cookie with SameSite=Lax, Secure in production, and a reasonable expiry (WooCommerce sessions typically last 48 hours). Avoid localStorage for the token — it is not sent with server-side requests and cannot be read by Server Components or Route Handlers.

// middleware.js — read cart token on every request
import { NextResponse } from 'next/server'

export function middleware(request) {
  const response = NextResponse.next()
  const cartToken = request.cookies.get('wc-cart-token')?.value

  // Make cart token available to Server Components via headers
  if (cartToken) {
    response.headers.set('x-cart-token', cartToken)
  }

  return response
}

Handling checkout

Checkout is the most complex part of any headless WooCommerce build. You have two main strategies, and the choice depends on how much control you need over the checkout experience.

Strategy 1: WooCommerce Store API checkout

The Store API includes a /checkout endpoint that processes the entire order — billing/shipping details, payment, and order creation — in a single POST request. This is the path of least resistance. WooCommerce handles payment gateway processing, tax calculations, coupon validation, and stock management. Your frontend collects the form data and submits it.

The limitation is payment gateways. Not all WooCommerce payment gateways support the Store API checkout flow. Stripe and PayPal (via WooCommerce Payments or the official extensions) work. Niche gateways that rely on server-side redirects or iframes may not. Check your payment gateway's documentation for Store API compatibility before committing to this approach.

Strategy 2: Redirect to WooCommerce checkout

The pragmatic fallback: build your catalogue and cart in Next.js, but redirect customers to the standard WooCommerce checkout page for payment. This guarantees compatibility with every payment gateway and every WooCommerce checkout plugin. The downside is an abrupt context switch — customers leave your fast Next.js frontend for a traditional WordPress page. You can mitigate this with a custom checkout template that matches your headless storefront's design, but it is still a compromise. Our migration guide covers both strategies in detail.

Common pitfalls and how to avoid them

After helping developers debug headless WooCommerce builds, these are the issues that come up repeatedly. Save yourself the debugging time.

CORS configuration

If your Next.js frontend is on store.com and WordPress is on api.store.com, browsers will block cross-origin requests unless WordPress sends the correct CORS headers. Add this to your WordPress theme's functions.php or a custom plugin:

// WordPress functions.php
add_action('rest_api_init', function () {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
    add_filter('rest_pre_serve_request', function ($value) {
        header('Access-Control-Allow-Origin: https://your-nextjs-domain.com');
        header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Cart-Token, Nonce');
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Expose-Headers: Nonce, Cart-Token');
        return $value;
    });
}, 15);

Do not use wildcard origins

Setting Access-Control-Allow-Origin: * is tempting but dangerous. It allows any website to make authenticated requests to your WooCommerce API. Always specify your exact frontend domain. If you have multiple environments (staging, production), use conditional logic to allow only known origins.

Cookie and session handling

WooCommerce sessions rely on cookies. In a headless setup, the frontend and backend are on different domains, so cookies set by WordPress will not be sent with requests from your Next.js app. This is why the Store API uses a token-based approach instead of cookies. If you see cart contents disappearing between requests, check that you are persisting and sending the cart token correctly.

Session expiry

WooCommerce sessions expire after 48 hours by default (configurable via the wc_session_expiration filter). If a customer adds items to their cart and returns two days later, the session may be gone. Your frontend needs to handle this gracefully — detect the expired session, create a new one, and show the customer an empty cart rather than a cryptic error.

Rate limiting and caching

Without caching, every page load triggers API requests to WordPress. Under traffic, this will overload your WordPress server. Use Next.js's built-in caching (revalidate for ISR, unstable_cache for data cache) aggressively for product and category data. Cart operations cannot be cached (they are user-specific), but catalogue data should be. See our hosting strategy guide for backend infrastructure recommendations.

Recommended architecture

After working through authentication, sessions, and cart management, here is the architecture that balances reliability with developer experience for a WooCommerce REST API + Next.js build.

  • Server Components with consumer keys for product, category, and content data
  • WooCommerce Store API for all cart and checkout operations
  • Cart token stored in an HTTP-only cookie for session persistence
  • React Context for global cart state with optimistic updates
  • Next.js Route Handlers as a proxy layer for sensitive operations
  • ISR (revalidate) for product pages — static performance with fresh data
  • CORS locked to your specific frontend domain
  • All secrets in environment variables, never prefixed with NEXT_PUBLIC_

This setup keeps authentication credentials server-side, uses the purpose-built Store API for cart operations, and leverages Next.js's rendering model for performance. It works without additional WordPress plugins (no JWT, no CoCart) and scales well because product pages are statically generated.

For a deeper look at the overall architecture, read our headless WordPress guide. If you are still evaluating whether to use the REST API or GraphQL for data fetching, our REST API vs WPGraphQL comparison breaks down the trade-offs.

How WPBundle handles this

Everything described in this guide — authentication, session management, cart persistence, CORS, checkout integration — is implementation work that WPBundle handles out of the box. The companion WordPress plugin configures the API layer, manages sessions, and extends default endpoints. The Next.js frontend ships with cart context, token management, and optimised data fetching already wired up.

You do not need to write your own Store API wrapper, debug CORS headers, or figure out cart token persistence. WPBundle is the plumbing so you can focus on the storefront. If you are planning a migration to headless WooCommerce, it removes the most time-consuming part of the build — the integration layer between Next.js and WooCommerce.

Ready to go headless?

Join the WPBundle waitlist and get beta access completely free.

Join the Waitlist