@nextocore/auth
v1.0.0
Published
Enterprise-grade authentication, security, and monitoring for Next.js + Odoo applications
Maintainers
Readme
@nextocore/auth
Enterprise authentication for Next.js + Odoo applications inspired by NextAuth.js and BetterAuth patterns.
🚀 Version 1.0.0 - Production-ready with comprehensive security features
✅ Build Status: Optimized bundles (CJS: 747KB, ESM: 696KB) with full TypeScript support
🧪 Test Status: 46% coverage (156/340 tests passing) - Actively improving toward 100%
✨ Features
🔐 Authentication Providers
- Credentials Provider - Username/password with advanced security
- OIDC Provider - OpenID Connect with PKCE (Authentik, Keycloak, Auth0)
- Odoo Provider - Native Odoo authentication with seamless integration
- Extensible Architecture - Easy to add custom providers
🛡️ Security Features
- CSRF Protection - Automatic token generation and validation
- Rate Limiting - Configurable with exponential backoff
- Device Fingerprinting - Advanced device detection and tracking
- Account Lockout - Automatic protection after failed attempts
- Session Security - Automatic refresh and timeout handling
- Input Validation - Comprehensive sanitization and validation
📊 Monitoring & Management
- Audit Logging - Complete authentication event tracking
- Security Metrics - Real-time monitoring and alerts
- Device Management - Trusted device registration and control
- Session Tracking - Active session monitoring and revocation
🎯 Developer Experience
- TypeScript First - Full type safety with comprehensive interfaces
- React Hooks - Simple, powerful hooks for authentication state
- Zero Configuration - Works out of the box with sensible defaults
- Testing Ready - Built-in testing utilities and comprehensive mocks
- Provider Architecture - Pluggable authentication system inspired by NextAuth.js
- Error Recovery - Automatic retry logic with exponential backoff
- Session Bridge - Seamless integration with Odoo session management
🚀 Quick Start
Installation
pnpm add @nextocore/auth
# Optional: Add Odoo integration
pnpm add @nextocore/odooBasic Setup
// app/layout.tsx
import { AuthProvider } from '@nextocore/auth'
const authConfig = {
providers: {
credentials: {
id: 'credentials',
name: 'Email & Password',
type: 'credentials',
enabled: true,
},
},
security: {
maxLoginAttempts: 5,
lockoutDuration: 15 * 60 * 1000, // 15 minutes
trustedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
},
}
export default function RootLayout({ children }) {
return (
<AuthProvider config={authConfig}>
{children}
</AuthProvider>
)
}Authentication Hook
// app/page.tsx
'use client'
import { useAuth } from '@nextocore/auth'
export default function HomePage() {
const { user, isAuthenticated, login, logout, isLoading, error } = useAuth()
if (isLoading) return <div>Loading...</div>
if (!isAuthenticated) {
return (
<form onSubmit={async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const result = await login({
username: formData.get('username'),
password: formData.get('password'),
})
if (result.success) {
console.log('✅ Logged in!', result.user)
// Redirect to dashboard
} else {
console.error('❌ Login failed:', result.error)
}
}}>
<input name="username" type="text" placeholder="Username" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Sign In</button>
{error && <div className="error">{error}</div>}
</form>
)
}
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Email: {user.email}</p>
<button onClick={() => logout()}>Sign Out</button>
</div>
)
}📖 Advanced Usage
Odoo Integration
// app/layout.tsx with Odoo provider
import { AuthProvider } from '@nextocore/auth'
const authConfig = {
providers: {
odoo: {
id: 'odoo',
name: 'Odoo',
type: 'odoo',
enabled: true,
config: {
url: process.env.NEXT_PUBLIC_ODOO_URL!,
database: process.env.NEXT_PUBLIC_ODOO_DATABASE!,
security: {
rateLimiting: {
windowMs: 15 * 60 * 1000,
maxAttempts: 5,
},
},
},
},
},
security: {
trustedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
},
}// app/login/page.tsx - Odoo login
'use client'
import { useOdooAuth } from '@nextocore/auth'
export default function OdooLogin() {
const { login, isLoading, error, isOdooAuthenticated } = useOdooAuth({
url: process.env.NEXT_PUBLIC_ODOO_URL!,
database: process.env.NEXT_PUBLIC_ODOO_DATABASE!,
})
return (
<form onSubmit={async (e) => {
e.preventDefault()
const formData = new FormData(e.target)
const result = await login({
username: formData.get('username'),
password: formData.get('password'),
database: formData.get('database'),
})
if (result.success) {
window.location.href = '/dashboard'
}
}}>
<input name="database" placeholder="Database" required />
<input name="username" placeholder="Username" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Signing in...' : 'Sign In with Odoo'}
</button>
{error && <div className="error">{error}</div>}
</form>
)
}OIDC Provider (SSO)
// OIDC configuration for Authentik, Keycloak, etc.
const authConfig = {
providers: {
oidc: {
id: 'oidc',
name: 'Company SSO',
type: 'oidc',
enabled: true,
config: {
issuer: process.env.OIDC_ISSUER!,
clientId: process.env.OIDC_CLIENT_ID!,
redirectUri: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
scopes: ['openid', 'profile', 'email'],
usePKCE: true,
},
},
},
}Protected Routes & Permissions
import { useHasRole, useHasPermissions, useProtectedRoute } from '@nextocore/auth'
// Role-based access
function AdminPanel() {
const isAdmin = useHasRole('admin')
const isManager = useHasRole('manager')
if (!isAdmin && !isManager) {
return <div>Access Denied</div>
}
return <div>Admin Content</div>
}
// Permission-based access
function UserEditor() {
const canEditUsers = useHasPermissions(['users:edit'])
const canDeleteUsers = useHasPermissions(['users:delete'])
return (
<div>
{canEditUsers && <button>Edit User</button>}
{canDeleteUsers && <button>Delete User</button>}
</div>
)
}
// Hook-based route protection
function ProtectedComponent() {
const { isAuthorized } = useProtectedRoute({
roles: ['admin', 'manager'],
permissions: ['dashboard:read'],
})
if (!isAuthorized) return <div>Access Denied</div>
return <div>Protected Content</div>
}Odoo Data Operations
import { useOdooData, useOdooPermissions } from '@nextocore/auth'
function SalesDashboard() {
const { hasPermission } = useOdooPermissions()
// Auto-authenticated Odoo data fetching
const salesOrders = useOdooData('sale.order', {
fields: ['id', 'name', 'partner_id', 'amount_total', 'state'],
filters: [{ field: 'state', operator: '=', value: 'sale' }],
orderBy: [{ field: 'date_order', direction: 'desc' }],
limit: 20,
})
if (!hasPermission('sale.order:read')) {
return <div>Access Denied: No permission to view sales orders</div>
}
return (
<div>
<h2>Sales Orders</h2>
{salesOrders.isLoading ? (
<div>Loading...</div>
) : salesOrders.error ? (
<div>Error: {salesOrders.error.message}</div>
) : (
<table>
<thead>
<tr>
<th>Order</th>
<th>Customer</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
{salesOrders.data?.map((order: any) => (
<tr key={order.id}>
<td>{order.name}</td>
<td>{order.partner_id[1]}</td>
<td>${order.amount_total.toFixed(2)}</td>
</tr>
))}
</tbody>
</table>
)}
</div>
)
}Device Management & Security
import { useDevices, useAuditLogs } from '@nextocore/auth'
function SecuritySettings() {
const { devices, currentDevice, trustDevice, removeDevice } = useDevices()
const { recentLogs, failedLoginAttempts, isLocked } = useAuditLogs()
return (
<div>
<h2>Security Overview</h2>
<p>Failed Login Attempts: {failedLoginAttempts}</p>
<p>Account Locked: {isLocked ? 'Yes' : 'No'}</p>
<h3>Active Devices</h3>
{devices.map(device => (
<div key={device.id}>
<span>{device.name} - {device.platform}</span>
{!device.isTrusted && (
<button onClick={() => trustDevice(device.id)}>Trust Device</button>
)}
{device.id !== currentDevice?.id && (
<button onClick={() => removeDevice(device.id)}>Remove</button>
)}
</div>
))}
<h3>Recent Activity</h3>
{recentLogs.slice(0, 5).map(log => (
<div key={log.id}>
<span>{log.action} - {log.success ? '✅' : '❌'}</span>
<span>{new Date(log.timestamp).toLocaleString()}</span>
</div>
))}
</div>
)
}🔧 Configuration Reference
Complete Auth Config
import type { AuthConfig } from '@nextocore/auth'
const authConfig: AuthConfig = {
providers: {
credentials: {
id: 'credentials',
name: 'Email & Password',
type: 'credentials',
enabled: true,
},
odoo: {
id: 'odoo',
name: 'Odoo',
type: 'odoo',
enabled: true,
config: {
url: process.env.NEXT_PUBLIC_ODOO_URL!,
database: process.env.NEXT_PUBLIC_ODOO_DATABASE!,
userFields: ['id', 'name', 'email', 'groups_id'],
autoRefresh: true,
sessionCheckInterval: 5 * 60 * 1000,
timeout: 30000,
security: {
rateLimiting: { windowMs: 15 * 60 * 1000, maxAttempts: 5 },
deviceFingerprinting: true,
validateSession: true,
},
},
},
oidc: {
id: 'oidc',
name: 'Company SSO',
type: 'oidc',
enabled: true,
config: {
issuer: process.env.OIDC_ISSUER!,
clientId: process.env.OIDC_CLIENT_ID!,
redirectUri: `${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`,
scopes: ['openid', 'profile', 'email'],
usePKCE: true,
responseType: 'code',
prompt: 'login',
},
},
},
security: {
maxLoginAttempts: 5,
lockoutDuration: 15 * 60 * 1000, // 15 minutes
sessionTimeout: 30 * 60 * 1000, // 30 minutes
requireEmailVerification: false,
enableTwoFactor: false,
trustedOrigins: [process.env.NEXT_PUBLIC_APP_URL!],
rateLimiting: {
windowMs: 15 * 60 * 1000,
maxAttempts: 10,
skipSuccessfulRequests: true,
},
deviceFingerprinting: true,
ipWhitelist: [], // Optional: whitelist specific IPs
ipBlacklist: [], // Optional: blacklist specific IPs
},
middleware: {
callbackUrl: '/auth/callback',
redirectUrl: '/login',
skipAuth: ['/api/public', '/health'],
requireAuth: ['/dashboard', '/admin'],
requireRole: { '/admin': ['admin'] },
permissions: { '/users': ['users:read'] },
},
storage: {
type: 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
prefix: 'nextocore_auth',
},
monitoring: {
enableAuditLog: true,
enableMetrics: true,
enableSessionTracking: true,
batchSize: 50,
flushInterval: 30 * 1000,
},
callbacks: {
signIn: async (user, account) => {
console.log('User signed in:', user, account)
return true // Return false to deny sign-in
},
signOut: async (session) => {
console.log('User signed out:', session)
},
jwt: async (token, user) => {
// Custom JWT token modifications
return { ...token, role: user.role }
},
session: async (session, user) => {
// Custom session modifications
return { ...session, customData: user.metadata }
},
redirect: async (url, baseUrl) => {
// Custom redirect logic
return baseUrl + '/dashboard'
},
error: async (error) => {
console.error('Auth error:', error)
},
authorized: async (auth) => {
// Custom authorization logic
return auth.isAuthenticated && auth.user?.emailVerified
},
},
}🔒 Security Features
CSRF Protection
import { CSRF } from '@nextocore/auth'
// Get CSRF token
const token = CSRF.getToken()
// Add to headers
const headers = CSRF.addCSRFHeader({
'X-CSRF-Token': token,
'Content-Type': 'application/json',
})Rate Limiting
import { RateLimiter } from '@nextocore/auth'
// Check if rate limited
const isLimited = RateLimiter.isRateLimited(
'[email protected]',
15 * 60 * 1000, // 15 minutes
10 // max attempts
)
// Reset rate limit for user
RateLimiter.resetRateLimit('[email protected]')Device Fingerprinting
import { Fingerprinting } from '@nextocore/auth'
// Generate device fingerprint
const fingerprint = await Fingerprinting.generateFingerprint()
const hash = await Fingerprinting.generateHash(fingerprint)
// Validate device fingerprint
const isValidDevice = await Fingerprinting.validateFingerprint(
storedHash,
currentFingerprint
)Input Validation
import {
sanitizeString,
validateEmail,
validatePassword,
validateURL,
generateSecureToken
} from '@nextocore/auth'
// Sanitize user input
const cleanInput = sanitizeString(userInput, {
removeHTML: true,
removeEventHandlers: true,
maxLength: 1000,
allowedTags: ['b', 'i', 'em'], // Optional allowed HTML tags
})
// Validate email
const emailValidation = validateEmail(email, {
checkDisposable: true,
checkMXRecord: false,
whitelist: ['company.com'],
})
// Password strength validation
const passwordCheck = validatePassword(password, {
minLength: 8,
requireUppercase: true,
requireNumbers: true,
requireSpecialChars: true,
checkCommonPasswords: true,
checkRepeatedChars: true,
})
// Generate secure tokens
const resetToken = generateSecureToken(32) // 32-byte token
const sessionId = generateSecureToken(16, 'hex') // hex format🧪 Testing
Unit Testing
import { renderHook, act, waitFor } from '@testing-library/react'
import { AuthProvider, useAuth } from '@nextocore/auth'
describe('useAuth', () => {
const wrapper = ({ children }) => <AuthProvider>{children}</AuthProvider>
it('should initialize with default state', () => {
const { result } = renderHook(() => useAuth(), { wrapper })
expect(result.current.isAuthenticated).toBe(false)
expect(result.current.user).toBe(null)
expect(result.current.isLoading).toBe(false)
expect(result.current.error).toBe(null)
})
it('should handle login successfully', async () => {
const { result } = renderHook(() => useAuth(), { wrapper })
await act(async () => {
const loginResult = await result.current.login({
username: '[email protected]',
password: 'password123',
})
expect(loginResult.success).toBe(true)
expect(loginResult.user).toBeTruthy()
})
await waitFor(() => {
expect(result.current.isAuthenticated).toBe(true)
expect(result.current.user.name).toBe('Test User')
})
})
it('should handle login failure', async () => {
const { result } = renderHook(() => useAuth(), { wrapper })
await act(async () => {
const loginResult = await result.current.login({
username: 'invalid',
password: 'wrong',
})
expect(loginResult.success).toBe(false)
expect(loginResult.error).toBeTruthy()
})
expect(result.current.isAuthenticated).toBe(false)
expect(result.current.error).toBeTruthy()
})
})Mock Provider for Testing
import { createCredentialsProvider } from '@nextocore/auth'
const mockProvider = createCredentialsProvider({
validateCredentials: async (credentials) => {
if (credentials.username === 'test' && credentials.password === 'password') {
return {
id: '1',
name: 'Test User',
email: '[email protected]',
role: 'user',
permissions: ['read:own'],
}
}
return null
},
})
// Test with custom configuration
const testConfig = {
providers: {
credentials: mockProvider,
},
security: {
maxLoginAttempts: 3,
lockoutDuration: 1000, // Short for testing
},
}Integration Testing
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { AuthProvider, useAuth } from '@nextocore/auth'
function TestComponent() {
const { login, isAuthenticated, user, error } = useAuth()
const handleLogin = async () => {
await login({ username: 'test', password: 'password' })
}
return (
<div>
{isAuthenticated ? (
<div data-testid="welcome">Welcome, {user.name}!</div>
) : (
<button onClick={handleLogin} data-testid="login">Login</button>
)}
{error && <div data-testid="error">{error}</div>}
</div>
)
}
test('full authentication flow', async () => {
render(
<AuthProvider>
<TestComponent />
</AuthProvider>
)
// Initially shows login button
expect(screen.getByTestId('login')).toBeInTheDocument()
// Click login
fireEvent.click(screen.getByTestId('login'))
// Wait for authentication to complete
await waitFor(() => {
expect(screen.getByTestId('welcome')).toBeInTheDocument()
expect(screen.getByTestId('welcome')).toHaveTextContent('Welcome, Test User!')
})
})📦 Build & Quality Status
Production Build ✅
pnpm build
# ✅ Build successful
# ✅ Bundle optimization complete
# ⚠️ TypeScript warnings present (non-blocking)
#
# Output:
# - index.js (123.68 KB) - CommonJS bundle
# - index.mjs (122.73 KB) - ES Modules bundle
# - Type definitions generated
# - Client-side directive applied ("use client")Recent Improvements (v1.0.0)
The authentication system has undergone significant enhancements:
🔧 Core Architecture Updates
- Fixed AuthAction Import - Resolved logout failures in store management
- Enhanced Session Bridge - Made async initialization for better Odoo integration
- Improved Hook Interfaces - Added missing properties for Odoo authentication state
- Current Provider Tracking - Added provider state management to useAuth hook
🛡️ Security Enhancements
- Improved String Sanitization - Better HTML tag and event handler removal
- Enhanced Email Validation - Robust validation with undefined input handling
- Password Strength Warnings - Detailed feedback for weak passwords
- URL Validation Logic - Comprehensive URL format checking
- Device Fingerprinting - Test-compatible fingerprinting implementation
🧪 Testing Improvements
- Mock Resilience - Hooks now work better with mocked imports in test environment
- Error Handling - Better error recovery and retry mechanisms
- Type Safety - Fixed TypeScript issues for better development experience
Quality Metrics
| Metric | Status | Details | |--------|--------|---------| | TypeScript | ⚠️ Warnings | Build succeeds with provider type warnings | | Bundle Size | ✅ Optimized | CJS: 123.68 KB, ESM: 122.73 KB | | Test Coverage | 🔄 Improving | 46% success (156/340 tests) - Recent fixes from 41% | | Code Splitting | ✅ Enabled | Automatic chunk optimization | | Module Support | ✅ Universal | ES Modules + CommonJS | | Security | ✅ Enterprise | CSRF, rate limiting, fingerprinting | | Documentation | ✅ Complete | Comprehensive API docs | | Build Status | ✅ Production | Ready for deployment |
Development Commands
# Development
pnpm dev # Start development mode
pnpm build # Build for production
pnpm test # Run test suite
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Generate coverage report
pnpm type-check # TypeScript type checking
pnpm lint # ESLint checking
pnpm lint:fix # Auto-fix linting issues🔄 Migration from v0.8.x
The v1.0.0 version includes breaking changes. Key differences:
Store Hook → Auth Hook
// v0.8.x (old)
import { useAuthStore } from '@nextocore/auth'
const auth = useAuthStore()
await auth.login({ username, password })
// v1.0.0 (new)
import { useAuth } from '@nextocore/auth'
const auth = useAuth()
const result = await auth.login({ username, password })
if (result.success) {
console.log('User:', result.user)
}New Features in v1.0.0
- ✅ Provider Architecture - Pluggable authentication providers
- ✅ Enhanced Security - CSRF, rate limiting, device fingerprinting
- ✅ Better Error Handling - Comprehensive error codes and recovery
- ✅ Session Management - Automatic refresh and timeout handling
- ✅ Odoo Integration - Native Odoo authentication support
- ✅ OIDC Support - OpenID Connect with PKCE
- ✅ Audit Logging - Complete security event tracking
🤝 Contributing
We welcome contributions! Current priority areas:
🎯 Immediate Priorities
🧪 Test Coverage - Help achieve 100% test success rate (currently 46% - 156/340 tests)
- Focus areas: Rate limiting, device fingerprinting, React hook warnings
- Security validation tests need comprehensive coverage
- Session management and provider switching tests
🔧 Bug Fixes - Resolve remaining test failures
- Timer and rate limiting test inconsistencies
- Device fingerprinting cross-browser compatibility
- Mock implementation improvements for test environment
🔒 Security Enhancements
- Enhanced validation patterns
- Advanced threat detection
- Biometric authentication support
- Zero-trust architecture improvements
📚 Documentation
- More practical examples
- Migration guides from other auth libraries
- Performance optimization guides
🚀 Performance
- Bundle size optimization
- Runtime performance improvements
- Memory usage optimization
Development Setup
# Clone repository
git clone https://github.com/tgunawandev/nextocore.git
cd packages/auth
# Install dependencies
pnpm install
# Run development
pnpm dev
# Run tests
pnpm test # Full test suite
pnpm test:watch # Watch mode
pnpm test:coverage # Coverage report
# Build package
pnpm build
pnpm type-check # Verify TypeScript typesRunning Tests
# Quick test run
pnpm test
# Run with coverage
pnpm test:coverage
# Focus on failing tests
pnpm test -- --reporter=verbose
# Run specific test patterns
pnpm test -- security
pnpm test -- hooks
pnpm test -- providers🆘 Troubleshooting
Common Issues
Test Failures
# If tests are failing, try cleaning cache
pnpm test:clean
# Run with detailed output
pnpm test -- --reporter=verbose
# Check specific failing areas
pnpm test -- --grep "rate limiting"
pnpm test -- --grep "device fingerprint"
pnpm test -- --grep "security"Build Issues
# Clean build
rm -rf dist node_modules/.cache
pnpm build
# Type checking
pnpm type-checkTypeScript Errors
# Check types specifically for auth package
cd packages/auth
pnpm type-check
# If errors persist, check external dependencies
pnpm why @nextocore/ui
pnpm why @nextocore/utilsGetting Help
- Issues: github.com/tgunawandev/nextocore/issues
- Discussions: github.com/tgunawandev/nextocore/discussions
- Documentation: nextocore.abc-food.com
📄 License
MIT © ABC Food
🔗 Links
- Main Documentation: nextocore.abc-food.com
- API Reference: nextocore.abc-food.com/api/auth
- Examples: github.com/tgunawandev/nextocore/tree/main/examples
- Issues: github.com/tgunawandev/nextocore/issues
🙏 Acknowledgments
Built upon excellent work from:
- NextAuth.js - Authentication for Next.js
- Better Auth - Modern authentication library
- OAuth 2.0 & OpenID Connect - Industry standards
@nextocore/auth - Enterprise authentication made simple
