In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
A few weeks back, I found myself deep in the weeds of building a fully headless Shopify e-commerce store. Everything was going smoothly—until I hit that inevitable wall: user accounts. I needed the basics—login, registration, viewing account information, order history, the usual suspects. In a traditional Shopify setup, these are just there out of the box. But in a headless build? You’re suddenly flying without a safety net. Every auth flow and customer interaction becomes your responsibility.
Luckily, Shopify has a solution: the Customer Account API. On paper, it promises to give you all the account management features you know and expect, and you get to wire up fully custom interfaces and flows just for your users. Sounds awesome, right? The catch: there’s a learning curve, and more than a few “gotchas” along the way—from configuring your API access, handling tricky token flows, to making sure your customers actually stay signed in.
After spending way longer than I expected wrangling with setup, API quirks, and debugging my own mistakes, I decided to pull it all together in this guide—for future me, and for the next developer who goes headless and wonders “where are the login forms and order pages I was promised?”
In this article, I’ll walk you through everything you actually need to do to add a robust customer account system to your headless Shopify store: from OAuth setup, to secure logins, order history views, and all the hidden details in-between. If you want enterprise-grade security and total UX control, you’re in the right place. Let’s ship something awesome.
The Complete User Authentication Flow
Understanding the user journey: Before diving into code, let's walk through exactly what happens when a customer interacts with your authentication system. This flow shows how an anonymous visitor becomes an authenticated customer with full access to their account.
Step-by-Step User Journey
1. Initial Visit - Unauthenticated State
User arrives at your site (anonymous visitor)
Navigation shows "Login" button instead of account menu
Address Management: Customer can update shipping/billing addresses
Session persistence: Remains logged in across browser sessions
Integration with product browsing: Once authenticated, customers can seamlessly browse your product catalog and collections. The pagination patterns we covered in our Shopify Headless Storefront Cursor Pagination guide work perfectly with authenticated sessions, and the product count strategies help authenticated users navigate large catalogs efficiently.
11. Logout Process
User clicks logout button
Process: logout() function from auth context
Steps:
Calls POST /api/auth/logout to revoke tokens server-side
Clears all authentication cookies
Redirects to Shopify's logout endpoint for complete session termination
Returns user to your site in unauthenticated state
Visual Flow Representation
code
🌐 User visits site (unauthenticated)
↓
🔐 Clicks "Login" button
↓
🛡️ Redirected to Shopify auth server
↓
✅ User authenticates with Shopify
↓
🔄 Shopify redirects back with authorization code
↓
🔐 Your app exchanges code for tokens
↓
👤 Fetch customer data from Shopify
↓
🎉 User now authenticated & can access account
↓
📊 Order history, profile management available
↓
🔄 Background token refresh maintains session
↓
🚪 User logout → tokens revoked → back to step 1
Key Components in the Flow
Flow Step
Component/File
Purpose
Initial State
components/auth/auth-context.tsx
Manages authentication state
Login Button
components/auth/login-button.tsx
Triggers authentication flow
OAuth Redirect
lib/shopify/oauth-client.ts
Builds secure authorization URL
Callback Handler
app/auth/callback/page.tsx
Processes OAuth callback
Token Exchange
app/api/auth/callback/route.ts
Exchanges code for tokens
Session Storage
lib/auth/session.ts
Secure cookie management
Customer Data
lib/shopify/fetchers/customer-account/customer.ts
Fetches customer information
Account UI
components/account/
Account dashboard and features
Token Refresh
app/api/auth/refresh/route.ts
Automatic token renewal
Logout
app/api/auth/logout/route.ts
Secure session termination
📸 Screenshot Opportunity: Create a visual flowchart showing this user journey with screenshots of each step - the login button, Shopify's auth page, the callback URL, the authenticated state, and the account dashboard.
Why this flow matters: This comprehensive flow ensures security at every step while providing a seamless user experience. Each component has a specific responsibility, making the system maintainable and secure.
What You'll Accomplish
By following this guide, you'll implement:
User Authentication: Login and logout functionality
Account Creation: Customer registration with Shopify
Session Management: Secure token handling and automatic refresh
Order History: Retrieve and display customer orders
Profile Management: Update customer information and addresses
Full E-commerce Experience: Complete customer account functionality
📸 Screenshot Opportunity: Take a screenshot of your final customer account dashboard showing the login/logout button, order history, and profile sections. This gives readers a clear vision of what they'll build.
Understanding Shopify's Three Core APIs
Why we need to understand this: Before diving into implementation, it's crucial to understand where the Customer Account API fits in Shopify's ecosystem. Many developers get confused about which API to use for what purpose, leading to security issues or architectural problems. Understanding these three APIs will help you make informed decisions about your implementation strategy.
Shopify provides three main APIs that form the cornerstone of headless e-commerce development:
Purpose: Metaobjects, store configuration, inventory management
Access: Server-only (contains sensitive data)
Authentication: Admin access token (must be kept secret)
3. Customer Account API
Usage: Customer authentication and data management
Purpose: User authentication, order history, profile management
Access: OAuth-based authentication
Authentication: Customer access tokens with OAuth flow
Important: The Customer Account API is the latest addition to Shopify's API family, designed specifically for privacy protection and customer data handling.
📸 Screenshot Opportunity: Create a simple diagram or screenshot showing how these three APIs work together in your application. Show the Storefront API handling products, Admin API managing configurations, and Customer Account API handling user authentication.
Architecture Overview
Why architecture matters: A well-designed architecture is the foundation of maintainable code. The Customer Account API implementation touches many parts of your application - from client-side components to server-side API routes. Understanding this architecture upfront will help you navigate the code and make modifications confidently.
The implementation follows a layered architecture:
📸 Screenshot Opportunity: Take a screenshot of your project's file structure in VS Code, highlighting the key directories mentioned in the architecture diagram (components/auth, app/api/auth, lib/shopify, lib/auth).
Environment Configuration
Why environment configuration is critical: OAuth authentication requires precise configuration of redirect URIs and client credentials. A single misconfigured environment variable can break the entire authentication flow. This section ensures you have all the necessary credentials and URLs configured correctly before diving into the implementation.
📸 Screenshot Opportunity: Show your .env.local file (with sensitive values redacted) to demonstrate proper environment variable setup. Also capture the Shopify Partner Dashboard where you configure the Customer Account API app settings.
OAuth Implementation
OAuth Implementation
Why OAuth is essential: OAuth 2.1 with PKCE (Proof Key for Code Exchange) is the gold standard for secure authentication. It prevents common security vulnerabilities like authorization code interception and provides a secure way for your application to access customer data without storing passwords. Understanding this flow is crucial for maintaining security and compliance.
OAuth patterns across platforms: The OAuth 2.1 with PKCE flow used here is the same pattern we implement for other integrations. For backend-to-backend authentication scenarios, see securing MCP servers with OAuth, which uses Dynamic Client Registration and the same PKCE security model. For persistent offline access to third-party APIs, check Google OAuth refresh token persistence.
1. Client-Side OAuth Utilities
File: lib/shopify/oauth-client.ts
The client-side OAuth utilities handle the initial authentication flow:
Client-Safe: Uses only NEXT_PUBLIC_ environment variables
⚠️ Critical OAuth Configuration: The most common authentication failure is missing the required OAuth scope. During our implementation, we discovered that without the customer-account-api:full scope, Shopify rejects ALL requests regardless of token format:
Why this matters: The customer-account-api:full scope is essential for accessing customer account data. Without it, you'll encounter "Invalid token" errors that can be misleading because the token format might be correct, but the scope permissions are insufficient.
📸 Screenshot Opportunity: Capture the browser's Network tab during login showing the OAuth authorization URL being generated, highlighting the PKCE parameters (code_challenge, state, nonce) and the complete scope string.
2. Server-Side OAuth Operations
Why server-side operations are crucial: While the client initiates the OAuth flow, the server must handle the secure token exchange. This separation ensures that sensitive operations like token refresh and revocation happen in a secure environment where tokens can't be intercepted by malicious client-side code.
File: lib/shopify/oauth-server.ts
Server-side utilities handle token exchange and management:
Token Revocation: Secure logout with token invalidation
📸 Screenshot Opportunity: Show the server logs during token exchange, demonstrating the successful OAuth callback with token response (ensure to redact actual token values).
Session Management
Why secure session management matters: Once you have authentication tokens, you need to store them securely and manage their lifecycle. Poor session management is a common source of security vulnerabilities. HTTP-only cookies provide the perfect balance of security and usability - they're inaccessible to JavaScript (preventing XSS attacks) but automatically included in requests.
Secure Cookie-Based Sessions
File: lib/auth/session.ts
The session management system uses secure HTTP-only cookies:
Token Formatting: Automatic shcat_ prefix for Shopify tokens
📸 Screenshot Opportunity: Show the browser's Developer Tools > Application > Cookies section, highlighting the secure authentication cookies with their httpOnly and secure flags.
Token Lifecycle Management
Why token lifecycle management is important: Tokens expire for security reasons, but users shouldn't be logged out unexpectedly. Proper lifecycle management ensures seamless user experience by automatically refreshing tokens before they expire, while maintaining security by not keeping tokens longer than necessary.
typescript
// Check if token is expired (with 5-minute buffer)const isExpired = isTokenExpired(tokens.expiresAt);
// Get valid access token (auto-refresh if needed)const accessToken = awaitgetValidAccessToken();
// Update access token after refreshawaitupdateAccessToken(newAccessToken, expiresIn);
🔍 Session Persistence Troubleshooting: A common issue we encountered was users appearing logged in during the session but showing as unauthenticated after page refresh. This typically happens when the initial session loading fails:
The Problem: Auth context doesn't properly load session on app initialization
typescript
// ❌ Missing credentials can cause session loading to failconst response = awaitfetch("/api/auth/session", {
method: "GET",
// Missing credentials: "include"
});
The Solution: Ensure cookies are included in session requests
typescript
// ✅ Include credentials for proper cookie handlingconst response = awaitfetch("/api/auth/session", {
method: "GET",
credentials: "include", // Essential for cookie-based sessions
});
Why this happens: HTTP-only cookies require the credentials: "include" option to be sent with requests. Without this, the session endpoint can't access the authentication cookies, causing the user to appear logged out even though their session is valid.
Quick debugging check: If users are logged out after refresh, inspect the session request in Network tab and verify that cookies are being sent with the request.
Authentication Flow Implementation
Why understanding the full flow is crucial: Authentication isn't just about the initial login - it's about managing the entire user journey from login to logout, including error handling and token refresh. This comprehensive flow ensures users have a smooth experience while maintaining security throughout their session.
1. Login Flow
The login journey: When a user clicks "Login," they're redirected to Shopify's secure authentication server. After successful authentication, they're redirected back to your app with an authorization code. This code is then exchanged for access tokens that allow your app to access their account data.
📸 Screenshot Opportunity: Show the Shopify authentication page that users see during login, then capture the callback URL in the browser's address bar showing the authorization code parameter.
2. OAuth Callback Handler
What happens during the callback: After users authenticate with Shopify, they're redirected back to your app with an authorization code. This callback handler validates the response, exchanges the code for tokens, and establishes the user session. This is where the magic happens - transforming a temporary code into permanent access to the customer's account.
🔧 Common Token Format Issues: During development, we encountered several token-related issues that can be confusing to debug:
Issue 1: Missing Token Prefix
typescript
// ❌ Problem - Using raw OAuth tokenconst customer = awaitgetCustomer(tokenResponse.access_token);
// ✅ Solution - Format token with required prefixconst formattedToken = formatAccessToken(tokenResponse.access_token);
const customer = awaitgetCustomer(formattedToken);
Issue 2: Incorrect Authorization Header
typescript
// ❌ Problem - Standard OAuth Bearer formatheaders: {
Authorization: `Bearer ${token}`;
}
// ✅ Solution - Shopify Customer Account API formatheaders: {
Authorization: token; // Direct token, no "Bearer" prefix
}
Why these issues occur: The Customer Account API uses a unique authentication format that differs from standard OAuth implementations. The token must be prefixed with shcat_ and sent directly in the Authorization header without the "Bearer" prefix.
📸 Screenshot Opportunity: Show the Network tab during the OAuth callback, highlighting the POST request to /api/auth/callback with the authorization code and the successful response with customer data. Also capture any token format errors in the console.
3. Logout Implementation
Why proper logout is essential: Logout isn't just about clearing local state - it's about completely terminating the user's session both in your app and with Shopify. This involves token revocation, cookie clearing, and redirecting users to Shopify's logout endpoint to ensure they're fully logged out of their Shopify account too.
📸 Screenshot Opportunity: Show the logout flow in action - capture the user clicking logout, then show the redirect to Shopify's logout page, followed by the return to your site in a logged-out state.
Customer Data Management
Why GraphQL for customer data: The Customer Account API uses GraphQL, which allows you to request exactly the data you need in a single request. This is particularly important for customer data where you might need profile information, addresses, and order history - GraphQL lets you fetch all of this efficiently while respecting privacy boundaries.
1. GraphQL Queries
Building efficient queries: These queries are designed to fetch customer data with minimal network requests while respecting Shopify's rate limits and the customer's privacy settings.
File: lib/shopify/queries/customer.ts
graphql
# Get customer profilequery getCustomer {
customer {
id
displayName
firstName
lastName
emailAddress {
emailAddress
}
phoneNumber {
phoneNumber
}
defaultAddress {
id
firstName
lastName
address1
address2
city
province
country
zip
}}}# Get customer ordersquery getCustomerOrders($first: Int =10){
customer {
orders(first:$first) {
nodes {
id
name
processedAt
totalPrice {
amount
currencyCode
}}}}}
Pagination considerations: The Customer Account API uses cursor-based pagination similar to the Storefront API. If you're implementing order history with large order counts, the pagination strategies from our Shopify Headless Storefront Cursor Pagination guide apply here as well - just replace product nodes with order nodes.
📸 Screenshot Opportunity: Show the GraphQL Playground or your IDE with the customer queries, highlighting the structured data that gets returned for customer profile and orders.
2. Customer Data Fetching
How data fetching works: The fetching functions abstract away the complexity of token management and API calls, providing a clean interface for your components to request customer data. They handle token validation, automatic refresh, and error management transparently.
🚨 GraphQL Schema Gotchas: One frustrating issue we encountered was using fields that don't exist in the Customer Account API. This commonly happens when copying queries from Storefront API examples:
Fields that DON'T exist in Customer Account API:
typescript
// ❌ These fields will cause GraphQL errors:
customer {
createdAt // Not available
updatedAt // Not available
numberOfOrders // Not available
addresses {
phone // Not available in CustomerAddress
}
}
Fields that DO exist:
typescript
// ✅ Use these supported fields:
customer {
id
displayName
firstName
lastName
emailAddress {
emailAddress
}
phoneNumber {
phoneNumber
}
defaultAddress {
id
firstName
lastName
address1
address2
city
province
country
zip
}
}
Debugging tip: If you encounter "Field doesn't exist" errors, check the Customer Account API GraphQL schema for the exact field names and structure.
📸 Screenshot Opportunity: Show the Network tab during a customer data fetch, highlighting the GraphQL request with the customer token and the returned customer data structure. Also capture any GraphQL field errors in the console.
Order History Implementation
Why order history is crucial: Order history is often the primary reason customers create accounts. A well-implemented order history gives customers confidence in your store and reduces support requests. It needs to be fast, accurate, and present information in a user-friendly way.
Order Display Component
Creating user-friendly order displays: The order component transforms raw GraphQL data into a readable, clickable interface. It handles edge cases like empty order lists and provides clear navigation to detailed order views.
Performance optimization: For customers with extensive order histories, you'll want to implement the pagination patterns detailed in our cursor pagination guide. The same GraphQL cursor techniques that work for product listings apply to order history, ensuring smooth performance even with hundreds of orders.
📸 Screenshot Opportunity: Show the final order history component in action - display the list of orders with order numbers, dates, and prices, demonstrating how the raw data is transformed into a user-friendly interface.
API Routes Structure
Why API routes are the backbone: API routes handle all server-side authentication logic, from OAuth callbacks to session management. They're the bridge between your React components and Shopify's APIs, handling security, token management, and data transformation in a secure server environment.
Authentication Endpoints
Understanding the endpoint structure: Each endpoint has a specific purpose in the authentication flow. Understanding when and how to use each endpoint is crucial for implementing robust authentication.
📸 Screenshot Opportunity: Show your file structure in VS Code with the app/api/auth/ directory expanded, highlighting all the authentication endpoints and their purpose.
Key API Route Implementations
Critical implementations explained: These are the core implementations that make authentication work. Each handles a specific part of the authentication lifecycle and includes proper error handling and security measures.
📸 Screenshot Opportunity: Show the API routes in action through the Network tab, demonstrating the session validation and token refresh calls with their responses.
Component Architecture
Why component architecture matters: A well-structured component architecture makes your authentication system maintainable and reusable. The context provider pattern ensures authentication state is consistently available throughout your app while keeping components focused on their specific responsibilities.
Authentication Context Provider
Centralized authentication state: The authentication context provides a single source of truth for user authentication state across your entire application. It handles complex state management so your components can focus on rendering UI.
File: components/auth/auth-context.tsx
The authentication context provides application-wide auth state management:
Automatic Token Monitoring: Checks token expiration every 5 minutes
State Management: Centralized authentication state
Error Handling: Comprehensive error management
Session Persistence: Maintains auth state across page refreshes
📸 Screenshot Opportunity: Show the React Developer Tools with the AuthContext highlighted, demonstrating how the authentication state flows through your component tree.
Account Management Components
Organized account functionality: These components handle different aspects of the customer account experience, from viewing orders to managing profile information. Each component is focused on a specific user need while sharing common authentication state.
code
components/account/
├── account-server-layout.tsx # Server-side layout wrapper
├── client-tabs-wrapper.tsx # Client-side tab navigation
├── orders-tab.tsx # Order history display
├── profile-tab.tsx # Customer profile management
└── skeletons.tsx # Loading states
📸 Screenshot Opportunity: Show your account management interface with the different tabs (orders, profile) and loading states, demonstrating how the components work together to create a cohesive user experience.
Security Considerations
Why security is paramount: Customer account data is sensitive and regulated. A security breach can destroy customer trust and result in legal consequences. These security measures aren't optional - they're essential for protecting your customers and your business.
1. Token Security
Protecting the keys to the kingdom: Access tokens are like keys to your customers' accounts. Proper token security ensures that even if other parts of your application are compromised, customer data remains protected.
HTTP-Only Cookies: Prevents XSS attacks
Secure Transmission: HTTPS-only in production
Token Prefixing: Automatic shcat_ prefix for Shopify compatibility
Expiration Management: 5-minute buffer for token expiration
2. CSRF Protection
Preventing malicious requests: CSRF (Cross-Site Request Forgery) attacks can trick users into performing actions they didn't intend. These protections ensure that requests to your authentication endpoints are legitimate and come from your application.
State Parameter: Validates OAuth callback authenticity
Safeguarding sensitive information: Customer data must be protected at every layer of your application. These measures ensure that sensitive information never leaks and that access is properly controlled.
Server-Only Operations: Admin API calls restricted to server-side
Environment Variables: Sensitive data in environment variables
Token Revocation: Proper logout with token invalidation
📸 Screenshot Opportunity: Show your browser's Security tab or SSL certificate information, demonstrating the HTTPS connection and secure cookie settings that protect customer data.
Error Handling
Why robust error handling is crucial: Authentication errors can be confusing and frustrating for users. Good error handling provides clear feedback, maintains security, and helps users recover from problems. It's the difference between a user successfully logging in and abandoning your site.
Authentication Errors
Handling authentication failures gracefully: Authentication can fail for many reasons - expired tokens, network issues, or invalid credentials. Your error handling should guide users to resolution while maintaining security.
Managing API communication errors: GraphQL errors can provide detailed information about what went wrong, but they need to be handled carefully to avoid exposing sensitive information to users while still providing helpful feedback.
typescript
const res = awaitcustomerAccountApiRequest({
accessToken: token,
query: getCustomerQuery,
});
if (res.body.errors) {
thrownewError(res.body.errors[0].message);
}
📸 Screenshot Opportunity: Show error states in your UI - capture authentication errors, network failures, and loading states to demonstrate how your error handling provides clear user feedback.
File Structure Overview
Understanding the project organization: A well-organized file structure makes your authentication system maintainable and helps new developers understand how everything fits together. This structure separates concerns while keeping related files close together.
📸 Screenshot Opportunity: Show your complete project structure in VS Code with key authentication files highlighted, demonstrating how the architecture translates to actual file organization.
Testing and Validation
Why testing your authentication is critical: Authentication bugs can lock users out of their accounts or create security vulnerabilities. Proper testing ensures your authentication flow works reliably across different scenarios and edge cases.
Environment Validation
Catching configuration issues early: Environment validation prevents mysterious authentication failures by ensuring all required credentials are properly configured before your application starts.
Essential commands for development: These commands help you maintain code quality, generate types, and test your authentication implementation throughout development.
bash
# Start development server
pnpm dev
# Build for production
pnpm build
# Format code
pnpm prettier
# Check formatting
pnpm prettier:check
# Generate GraphQL types
pnpm codegen
📸 Screenshot Opportunity: Show your terminal running these commands, particularly the output from pnpm codegen showing GraphQL types being generated and any lint/format checks passing.
Common Issues and Solutions
Learning from real-world implementations: These are the exact issues we encountered during our Customer Account API implementation, along with the solutions that actually work. Each problem includes the symptoms, root causes, and step-by-step fixes.
1. "Invalid token, missing prefix shcat_" Error
Most Critical Issue: This error can have multiple layered causes that mask each other, making it particularly frustrating to debug.
Symptom: Error appears in console during OAuth callback, authentication seems to fail randomly
2. GraphQL Field Errors After Successful Authentication
Symptom: Authentication succeeds but GraphQL queries fail with "Field doesn't exist" errors
Root Cause: Using Storefront API fields that don't exist in Customer Account API
Unsupported Fields:
typescript
// ❌ These fields don't exist in Customer Account API:
customer {
createdAt // Not available
updatedAt // Not available
numberOfOrders // Not available
addresses {
phone // Not available in CustomerAddress
}
}
Solution: Use only supported fields from the Customer Account API schema
3. Authentication Context Not Updating
Symptom: OAuth completes successfully but isAuthenticated remains false
Verification: Ensure redirect URI in Shopify Partner Dashboard matches NEXT_PUBLIC_BASE_URL/auth/callback
Conclusion
What you've accomplished: You've now implemented a complete, secure, and production-ready customer authentication system using Shopify's Customer Account API. This implementation provides enterprise-grade security while maintaining full control over your user experience.
This implementation provides a complete, secure, and scalable customer authentication system using Shopify's Customer Account API. The architecture supports:
Full OAuth 2.0 Flow: With PKCE and security best practices
Secure Session Management: HTTP-only cookies with automatic refresh
Complete Customer Experience: Login, logout, profile management, and order history
Production-Ready: Proper error handling, security measures, and performance optimizations
The system offloads authentication complexity to Shopify while maintaining full control over the user experience in your headless application.
Your next steps: With this foundation in place, you can extend the system with additional features like address management, order tracking, wishlist functionality, and more. The architecture you've built is designed to scale with your business needs while maintaining security and performance.
Building a complete headless store: This Customer Account API implementation is one piece of a complete headless e-commerce solution. For the full picture, explore our other deep dives on implementing efficient product counts for category pages and mastering cursor pagination for smooth product browsing. Together, these guides provide the foundation for a high-performance, scalable headless Shopify store.
📸 Screenshot Opportunity: Show your final, fully functional customer account system in action - the login flow, authenticated dashboard, order history, and logout process working seamlessly together.