@payez/next-mvp
v3.6.0
Published
PayEz IDP authentication package for Next.js 14/15 with pre-built UI components and complete authentication flow.
Readme
@payez/next-mvp
PayEz IDP authentication package for Next.js 14/15 with pre-built UI components and complete authentication flow.
Version History
v2.4.2 (2025-11-14)
- Fixed: Redirect loop on token expiration - middleware now allows NextAuth JWT callback to refresh expired tokens instead of immediately redirecting to login, preventing infinite redirect loops when access tokens expire
v2.4.1 (2025-11-14)
- Fixed: Stale cookie detection - viability API now properly detects when JWT exists but Redis session is missing, preventing access with expired sessions
- Fixed: Token refresh field names - changed to PascalCase (
RefreshToken,AuthenticationMethods,AuthenticationLevel,TwoFactorMethod) to match IDP requirements, resolving 400 errors during token refresh
v2.4.0
- Auth-ready v2 handlers with pre-configured routes
- Redis session management improvements
- Token refresh optimizations
Features
- 🔐 Complete Authentication Flow - Login, logout, session management, password recovery
- 🎨 Pre-built UI Components - Ready-to-use login, recovery, and verify-code pages
- 🔄 Automatic Token Refresh - Built-in refresh token handling
- 🎭 Themeable - Customize branding, colors, and layout via ThemeProvider
- 📱 Responsive Design - Mobile-first Tailwind CSS components
- 🚀 Next.js 14/15 Ready - Works with App Router and React Server Components
- 🔒 Secure by Default - JWT-based authentication with PayEz IDP
Installation
npm install @payez/next-mvp next-auth
# or
yarn add @payez/next-mvp next-authPeer Dependencies
Ensure you have these installed:
npm install next@^14.0.0 next-auth@^4.24.7 react@^18.2.0 react-dom@^18.2.0Development Scripts
PayEz MVP includes convenient development scripts for getting started quickly:
npm run dev:local
Local Development Mode - Perfect for first-time setup and testing without an external IDP:
- Auto-generates
NEXTAUTH_SECRETfor you - No external IDP connection required
- Great for UI development and testing
npm run dev:localThis will:
- Generate a secure random
NEXTAUTH_SECRET - Display the secret (save it to your
.env.local) - Start the Next.js dev server on port 3000
npm run dev:broker
Broker Mode - Production-like authentication with PayEz IDP:
- Uses OAuth/OIDC flow with PayEz IDP
- Client assertion authentication
- Required for testing real authentication flows
- This is a core feature for PayEz MVP users
npm run dev:brokerThis will:
- Enable broker mode (
USE_BROKER_MODE=true) - Set client ID for IDP authentication
- Start the Next.js dev server on port 3000
- Automatically kills any existing process on port 3000
Custom Port & Client ID:
# In your consuming project's package.json
"scripts": {
"dev:broker": "pwsh -NoProfile -ExecutionPolicy Bypass -File node_modules/@payez/next-mvp/scripts/dev-broker.ps1 -ClientId 2 -Port 3400"
}Which Script to Use?
| Scenario | Script | When to Use |
|----------|--------|-------------|
| 🎨 UI Development | dev:local | Building components, testing UI flows |
| 🔐 Auth Testing | dev:broker | Testing real OAuth flows, token management |
| 🚀 Production-like | dev:broker | Testing with actual IDP integration |
Quick Start (App Router with UI Components)
1. Configure Next.js
Add to your next.config.ts:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ['@payez/next-mvp'],
};
export default nextConfig;2. Set Environment Variables
Create .env.local (or .env.development) in your project root:
# PayEz IDP Configuration (required)
CLIENT_ID=your_client_slug_from_payez # e.g., "payez_idp_admin_web"
IDP_URL=https://idp.payez.net # or http://localhost:32785 for local dev
# NextAuth trusts request headers for OAuth callback URLs
AUTH_TRUST_HOST=true
# NEXTAUTH_SECRET - DO NOT SET!
# The MVP broker fetches this securely from IDP at startup.
# Only set manually if you need to override the IDP-provided secret.
# Optional
REDIS_URL=redis://localhost:6379 # For session storage
NEXT_PUBLIC_IDP_BASE_URL=https://idp.payez.net # For client-side redirectsNote:
NEXTAUTH_SECRETis intentionally omitted. The MVP automatically fetches it from the IDP at startup using the broker pattern. See Environment Variables Reference for details.
3. Create NextAuth API Route
Create app/api/auth/[...nextauth]/route.ts:
/**
* NextAuth API Route Handler
* Uses pre-configured handler from @payez/next-mvp
*/
export { GET, POST } from '@payez/next-mvp/routes/auth/nextauth';4. Wrap Your App with Providers
Create app/providers.tsx:
'use client';
import { SessionProvider } from 'next-auth/react';
import { ThemeProvider } from '@payez/next-mvp/theme';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
<ThemeProvider>
{children}
</ThemeProvider>
</SessionProvider>
);
}Update app/layout.tsx:
import { Providers } from './providers';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}5. Add Authentication Pages
Login Page - app/account-auth/login/page.tsx:
'use client';
import LoginPage from '@payez/next-mvp/dist/pages/login';
export default function LoginPageWrapper() {
return <LoginPage />;
}Password Recovery - app/account-auth/recovery/page.tsx:
'use client';
import RecoveryPage from '@payez/next-mvp/dist/pages/recovery';
export default function RecoveryPageWrapper() {
return <RecoveryPage />;
}Verify Code (2FA) - app/account-auth/verify-code/page.tsx:
'use client';
import VerifyCodePage from '@payez/next-mvp/dist/pages/verify-code';
export default function VerifyCodePageWrapper() {
return <VerifyCodePage />;
}6. Protect Routes with Authentication
Server-side protection:
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await getServerSession();
if (!session) {
redirect('/account-auth/login');
}
return (
<div>
<h1>Welcome, {session.user?.email}</h1>
</div>
);
}Client-side hook:
'use client';
import { useSession } from 'next-auth/react';
export default function ProfilePage() {
const { data: session, status } = useSession();
if (status === 'loading') return <div>Loading...</div>;
if (status === 'unauthenticated') return <div>Access Denied</div>;
return <div>Logged in as {session?.user?.email}</div>;
}Advanced: V2 Middleware-Based Setup
For applications requiring middleware-based route protection:
1. Configure Public Routes
Create src/lib/auth.ts:
import { configurePublicRoutes, createAuthOptions } from '@payez/next-mvp';
import CredentialsProvider from 'next-auth/providers/credentials';
// Define routes that do NOT require authentication
const publicRoutes = [
'/',
'/login',
'/api/health',
'/api/public/*', // Wildcard example
];
configurePublicRoutes(publicRoutes);
export const authOptions = createAuthOptions({
credentialsProvider: CredentialsProvider,
// Additional NextAuthOptions can be passed here
});
export { authOptions as GET, authOptions as POST } from './auth';2. Setup NextAuth API Route
For Pages Router (pages/api/auth/[...nextauth].ts):
import NextAuth from 'next-auth';
import { authOptions } from '../../../src/lib/auth';
export default NextAuth(authOptions);For App Router (app/api/auth/[...nextauth]/route.ts):
export { GET, POST } from '../../../src/lib/auth';3. Implement Middleware
Create middleware.ts at project root:
import { createMvpMiddleware } from '@payez/next-mvp';
export default createMvpMiddleware();
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};4. Use fetchWithAuth for Protected API Calls
import { fetchWithAuth } from '@payez/next-mvp';
async function fetchData() {
try {
const response = await fetchWithAuth('/api/protected-data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Failed to fetch protected data:', error);
}
}5. Session Viability API Handler
For Pages Router (pages/api/session/viability.ts):
import viabilityHandler from '@payez/next-mvp/api-handlers/session/viability';
export default viabilityHandler;For App Router (app/api/session/viability/route.ts):
export { default as GET } from '@payez/next-mvp/api-handlers/session/viability';Custom Theming
Customize branding and colors:
// lib/mvp-theme-config.ts
import { ThemeConfig } from '@payez/next-mvp/theme';
const customTheme: ThemeConfig = {
branding: {
companyName: "Your Company",
logoUrl: "/logo.svg",
logoAlt: "Your Company Logo"
},
colors: {
primary: "#3b82f6", // Blue
primaryHover: "#2563eb",
secondary: "#10b981", // Green
danger: "#ef4444", // Red
text: "#1f2937",
textLight: "#6b7280",
background: "#ffffff",
backgroundAlt: "#f9fafb",
border: "#e5e7eb"
},
layout: {
maxWidth: "28rem", // max-w-md
padding: "1.5rem", // p-6
borderRadius: "0.5rem" // rounded-lg
}
};
export default customTheme;Then use in your Providers:
import customTheme from '@/lib/mvp-theme-config';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
<ThemeProvider theme={customTheme}>
{children}
</ThemeProvider>
</SessionProvider>
);
}API Routes
Access pre-built API handlers:
// Session validation
import { GET as validateSession } from '@payez/next-mvp/routes/auth/session';
// Token refresh
import { POST as refreshToken } from '@payez/next-mvp/routes/auth/refresh';
// Logout
import { POST as logout } from '@payez/next-mvp/routes/auth/logout';Environment Variables Reference
| Variable | Required | Description |
|----------|----------|-------------|
| CLIENT_ID | ✅ Yes | Your PayEz IDP client ID (string slug, e.g., payez_idp_admin_web) |
| IDP_URL | ✅ Yes | PayEz IDP base URL (e.g., https://idp.payez.net or http://localhost:32785) |
| AUTH_TRUST_HOST | ✅ Yes | Must be true - NextAuth derives OAuth URLs from request headers |
| NEXTAUTH_SECRET | 🔄 Broker | Do NOT set - fetched automatically from IDP at startup (see below) |
| CLIENT_SECRET | ⚠️ Legacy | Not needed with broker mode - IDP handles signing |
| NEXT_PUBLIC_IDP_BASE_URL | ⚠️ Optional | Public-facing IDP URL (for client-side redirects) |
| REDIS_URL | ⚠️ Optional | Redis connection string for session storage |
NEXTAUTH_SECRET Broker Flow
The MVP uses a "broker" pattern for NEXTAUTH_SECRET - the IDP securely provides it at startup rather than storing it in env files.
┌─────────────────────────────────────────────────────────────┐
│ App Startup (instrumentation.ts → ensureInitialized) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Is NEXTAUTH_SECRET set and non-empty? │
└─────────────────────────────────────────────────────────────┘
│ │
YES NO (recommended)
│ │
▼ ▼
┌─────────────┐ ┌─────────────────────────────────┐
│ Use as-is │ │ Fetch from IDP (Broker Mode) │
│ (override) │ │ 1. Sign client assertion │
│ │ │ 2. POST to /next-auth/secret │
│ │ │ 3. Set process.env at runtime │
└─────────────┘ └─────────────────────────────────┘Key points:
- Do NOT set
NEXTAUTH_SECRETin.envfiles - leave it undefined - The IDP provides the secret securely at startup via client assertion
- Secret is cached for 5 minutes, then re-fetched if needed
- If you DO set it manually, that value takes precedence (useful for overrides)
- The broker needs
CLIENT_IDandIDP_URLto fetch the secret
Troubleshooting
SessionProvider Error
If you see useSession must be wrapped in a <SessionProvider />:
- Ensure
transpilePackages: ['@payez/next-mvp']is innext.config.ts - Make sure all page components using PayEz components have
'use client'directive - Verify your app is wrapped with
<Providers>inlayout.tsx
Module Resolution Errors
If you get Module not found errors:
- Clear Next.js cache:
rm -rf .next - Reinstall dependencies:
npm install - Restart dev server
Authentication Not Working
- Verify
CLIENT_IDandIDP_URLare set correctly - Check
CLIENT_IDmatches your PayEz IDP configuration (use the slug, not numeric ID) - Verify IDP is running and reachable at
IDP_URL - Check server logs for broker startup messages - look for "NEXTAUTH_SECRET Successfully Fetched from IDP"
- If secret fetch fails, check IDP logs for client assertion errors
- Check browser console for error messages
NEXTAUTH_SECRET Issues
"NEXTAUTH_SECRET not available" error:
- This means the broker couldn't fetch the secret from IDP
- Check that
IDP_URLis correct and IDP is running - Verify
CLIENT_IDis registered in the IDP - Check network connectivity between your app and IDP
Cookie cross-contamination (localhost development):
- If running multiple apps on localhost (different ports), NextAuth cookies can conflict
- Clear
next-auth.*cookies in browser, or use incognito - Each app uses a unique cookie name based on CLIENT_ID to avoid conflicts
Docker/CI Deployment (IMPORTANT)
When consuming @payez/next-mvp via a local .tgz file in Docker builds, you must use a local path reference.
The Problem
If your package.json references the MVP like this:
"@payez/next-mvp": "file:../PayEz-Next-MVP/packages/next-mvp/payez-next-mvp-2.6.50.tgz"Docker builds will fail with:
npm error ENOENT: no such file or directory, open '/PayEz-Next-MVP/packages/next-mvp/payez-next-mvp-2.6.50.tgz'The relative path ../ resolves incorrectly inside the Docker container's /app working directory.
The Fix
Copy the
.tgzfile into your project root:cp ../PayEz-Next-MVP/packages/next-mvp/payez-next-mvp-*.tgz ./Update
package.jsonto use a local reference:"@payez/next-mvp": "file:./payez-next-mvp-2.6.50.tgz"Run
npm installto updatepackage-lock.jsonEnsure your Dockerfile copies the tgz:
COPY payez-next-mvp-*.tgz ./
Quick Fix Script
Run this in your consuming project to fix the path:
# Copy latest tgz
cp ../PayEz-Next-MVP/packages/next-mvp/payez-next-mvp-*.tgz ./
# Update package.json path (adjust version as needed)
sed -i 's|file:../PayEz-Next-MVP/packages/next-mvp/|file:./|g' package.json
# Regenerate lockfile
npm install --legacy-peer-depsAfter MVP Version Updates
When you update the MVP package version, repeat steps 1-3 to copy the new .tgz and update references.
Pre-Publishing Checklist (For Package Maintainers)
Before publishing this package to npm:
✅ Code Quality
- [x] All
@/path aliases converted to relative paths - [x] TypeScript compiles without errors:
npm run build - [x] No hardcoded configuration values (use env variables)
- [ ] Unit tests pass (if implemented)
📦 Package Configuration
CRITICAL FIXES NEEDED:
Fix
mainfield in package.json (Line 5):"main": "dist/index.js", // NOT "src/index.ts"Remove unused
tsc-aliasfrom devDependencies (Line 542):// DELETE this line: "tsc-alias": "^1.8.16"Add package metadata:
{ "description": "PayEz IDP authentication package for Next.js with ready-to-use login, recovery, and profile pages", "keywords": ["nextjs", "authentication", "next-auth", "payez", "idp", "oauth"], "author": "PayEz Team", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/your-org/PayEz-Next-MVP" }, "homepage": "https://github.com/your-org/PayEz-Next-MVP#readme", "bugs": { "url": "https://github.com/your-org/PayEz-Next-MVP/issues" } }
📝 Documentation
- [x] README.md is complete and accurate
- [ ] CHANGELOG.md exists with version history
- [ ] LICENSE file exists
🧪 Testing
- [ ] Test fresh installation in new Next.js 14 project
- [ ] Test fresh installation in new Next.js 15 project
- [ ] Verify all exported modules work
- [ ] Test with React 18 and React 19
- [ ] Test package build:
npm pack --dry-run
🚀 Publishing Steps
# 1. Fix package.json issues (see above)
# 2. Login to npm
npm login
# 3. Build package
npm run build
# 4. Preview what will be published
npm pack --dry-run
# 5. Test the tarball locally
npm install ./payez-next-mvp-2.3.1.tgz
# 6. Bump version (patch/minor/major)
npm version patch # 2.3.1 -> 2.3.2
npm version minor # 2.3.1 -> 2.4.0
npm version major # 2.3.1 -> 3.0.0
# 7. Publish to npm
npm publish
# 8. Create git tag and push
git push origin main
git push origin --tags📋 .npmignore
Create .npmignore to exclude unnecessary files:
src/
*.test.ts
*.test.tsx
tsconfig.json
.gitignore
.git
node_modules/
*.tgz
.env*🔒 Publish Configuration
Add to package.json:
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}📚 Complete Documentation
Core Guides
THEMING.md - Complete guide to customizing auth components
- Theme structure and configuration
- Creating light/dark modes
- Component-specific customization
- Common theming issues and solutions
CSS_VARIABLES_INTEGRATION.md - CSS variable injection for MVP components
- Why CSS variables are needed
- How to inject variables in your app
- Color conversion utilities
- Debugging CSS variable issues
THEME_EXAMPLES.md - Ready-to-use theme examples
- 7 pre-built professional themes
- Multi-brand theme support
- Dynamic light/dark mode
- Copy-paste theme configurations
Topics Covered
Theming
- ✅ Brand colors and logos
- ✅ Light and dark modes
- ✅ Font families and typography
- ✅ Layout and spacing customization
- ✅ Component-specific styling
- ✅ Multi-tenant/white-label setups
- ✅ Accessibility and contrast
CSS Variables
- ✅ Understanding CSS custom properties
- ✅ Manual variable injection
- ✅ Color conversion (Tailwind → hex)
- ✅ Theme provider pattern
- ✅ Dynamic theme switching
- ✅ Debugging in DevTools
Examples
- ✅ Corporate themes
- ✅ SaaS themes
- ✅ Creative/design themes
- ✅ Accessible high-contrast themes
- ✅ Multi-brand configurations
- ✅ Gradient backgrounds
- ✅ Custom branding
Quick Links
Getting Started with Themes:
- Start with THEMING.md - Quick Start section
- Copy a theme from THEME_EXAMPLES.md
- Follow integration pattern in CSS_VARIABLES_INTEGRATION.md
For Specific Tasks:
- Want to change colors? → THEMING.md - Creating a Theme
- Pages rendering white/no color? → CSS_VARIABLES_INTEGRATION.md - Troubleshooting
- Need a complete example? → THEME_EXAMPLES.md
- Debugging styling issues? → THEMING.md - Common Issues
License
MIT
Support
For issues and questions:
- GitHub Issues: https://github.com/your-org/PayEz-Next-MVP/issues
- Documentation: https://docs.payez.net
Contributing
Contributions welcome! Please read our contributing guidelines first.
Made with ❤️ by the PayEz Team
