{"_id":"@ayodyabanuka/next-auth-custom","_rev":"2-4d1e0a657d1474467eae43a2e4f20b5a","name":"@ayodyabanuka/next-auth-custom","dist-tags":{"latest":"3.0.0"},"versions":{"2.0.0":{"name":"@ayodyabanuka/next-auth-custom","version":"2.0.0","keywords":["nextjs","authentication","jwt","auth","middleware","typescript","app-router","custom-auth"],"author":{"name":"Ayodya Banuka"},"license":"MIT","_id":"@ayodyabanuka/next-auth-custom@2.0.0","maintainers":[{"name":"ayodyabanuka","email":"ayodyabanukafernando@gmail.com"}],"homepage":"https://github.com/ayodyabanuka/next-auth-custom#readme","bugs":{"url":"https://github.com/ayodyabanuka/next-auth-custom/issues"},"dist":{"shasum":"21ec0ba8611ac08b963951255eed01dc52b57881","tarball":"https://registry.npmjs.org/@ayodyabanuka/next-auth-custom/-/next-auth-custom-2.0.0.tgz","fileCount":17,"integrity":"sha512-hRewZmbA0r8pzYLC+2mIXRwTIdmVD9Ng58kHkttMARLQZfodReBTyqCrO5AR1eNsWBdic8HZIccfZi/PE6qiJg==","signatures":[{"sig":"MEQCIEZYzZYwAYMkP/HV95Bt3wwiQWjq57+yWp4CvkgiKBwLAiAE7pMyvUoxJLB1Ze1BVzK/H7eJmofA0yhAZuPsU9CFRg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":257289},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./middleware":{"types":"./dist/middleware.d.ts","import":"./dist/middleware.mjs","require":"./dist/middleware.js"}},"gitHead":"ceb2d91787f1e2c68feaf665c38e8be12122e13f","scripts":{"dev":"tsup --watch","build":"tsup","clean":"rm -rf dist","type-check":"tsc --noEmit","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"ayodyabanuka","email":"ayodyabanukafernando@gmail.com"},"repository":{"url":"git+https://github.com/ayodyabanuka/next-auth-custom.git","type":"git"},"_npmVersion":"10.8.3","description":"A helper library for custom JWT-based authentication in Next.js 13/14+ (App Router) without Firebase Auth","directories":{},"_nodeVersion":"22.2.0","dependencies":{"cookie":"^0.5.0","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","typescript":"^5.0.0","@types/node":"^20.0.0","@types/cookie":"^0.5.0","@types/bcryptjs":"^2.4.6","@types/jsonwebtoken":"^9.0.5"},"peerDependencies":{"next":"^13.0.0 || ^14.0.0","react":"^18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next-auth-custom_2.0.0_1753197307905_0.13168435172409199","host":"s3://npm-registry-packages-npm-production"}},"3.0.0":{"name":"@ayodyabanuka/next-auth-custom","version":"3.0.0","description":"A helper library for custom JWT-based authentication in Next.js 13/14+ (App Router) without Firebase Auth","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"},"./middleware":{"types":"./dist/middleware.d.ts","import":"./dist/middleware.mjs","require":"./dist/middleware.js"}},"scripts":{"build":"tsup","dev":"tsup --watch","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build","type-check":"tsc --noEmit"},"keywords":["nextjs","authentication","jwt","auth","middleware","typescript","app-router","custom-auth"],"author":{"name":"Ayodya Banuka"},"license":"MIT","peerDependencies":{"next":"^13.0.0 || ^14.0.0","react":"^18.0.0"},"dependencies":{"jsonwebtoken":"^9.0.2","bcryptjs":"^2.4.3","cookie":"^0.5.0"},"devDependencies":{"@types/jsonwebtoken":"^9.0.5","@types/bcryptjs":"^2.4.6","@types/cookie":"^0.5.0","@types/node":"^20.0.0","typescript":"^5.0.0","tsup":"^8.0.0"},"engines":{"node":">=18.0.0"},"repository":{"type":"git","url":"git+https://github.com/ayodyabanuka/next-auth-custom.git"},"bugs":{"url":"https://github.com/ayodyabanuka/next-auth-custom/issues"},"homepage":"https://github.com/ayodyabanuka/next-auth-custom#readme","_id":"@ayodyabanuka/next-auth-custom@3.0.0","gitHead":"aade7d564e3dc3823004287d2bad62f9e213776e","_nodeVersion":"22.2.0","_npmVersion":"10.8.3","dist":{"integrity":"sha512-KkFcwywCvsF/YrvRtHOYg/cjacwmEnn5KiBNxUdKH27BBv2P0fkQ+j6wJa1wSAEMhjuFl0ARLOH4Ovqmzy+2VQ==","shasum":"8ae4e5f42a110856aea47270ca84f32c310cba25","tarball":"https://registry.npmjs.org/@ayodyabanuka/next-auth-custom/-/next-auth-custom-3.0.0.tgz","fileCount":17,"unpackedSize":257297,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCdg7BbzBoQPHAu/hX3ZxufuY+fjldYM5nwMkSqN4f/EQIhAMbMyZx2HAbYPdTnPmgKYLh5fUq8K7OR4t9lMDrqbe4Y"}]},"_npmUser":{"name":"ayodyabanuka","email":"ayodyabanukafernando@gmail.com"},"directories":{},"maintainers":[{"name":"ayodyabanuka","email":"ayodyabanukafernando@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/next-auth-custom_3.0.0_1753197727839_0.3881374758667129"},"_hasShrinkwrap":false}},"time":{"created":"2025-07-22T15:15:07.768Z","modified":"2025-07-22T15:22:08.263Z","2.0.0":"2025-07-22T15:15:08.084Z","3.0.0":"2025-07-22T15:22:08.069Z"},"bugs":{"url":"https://github.com/ayodyabanuka/next-auth-custom/issues"},"author":{"name":"Ayodya Banuka"},"license":"MIT","homepage":"https://github.com/ayodyabanuka/next-auth-custom#readme","keywords":["nextjs","authentication","jwt","auth","middleware","typescript","app-router","custom-auth"],"repository":{"type":"git","url":"git+https://github.com/ayodyabanuka/next-auth-custom.git"},"description":"A helper library for custom JWT-based authentication in Next.js 13/14+ (App Router) without Firebase Auth","maintainers":[{"name":"ayodyabanuka","email":"ayodyabanukafernando@gmail.com"}],"readme":"# @ayodyabanuka/next-auth-custom\n\nA comprehensive helper library for custom JWT-based authentication in Next.js 13/14+ (App Router) without Firebase Auth.\n\n## Features\n\n- 🔐 **Custom JWT Authentication** - Full control over your authentication flow\n- 🔄 **Token Rotation** - Secure refresh token rotation (optional)\n- 🍪 **HttpOnly Cookies** - Secure token storage in cookies\n- 🛡️ **Route Protection** - Middleware for protecting routes\n- 📱 **Device Tracking** - Optional device-based session management\n- 🔧 **Database Agnostic** - Works with any database (Firestore, PostgreSQL, etc.)\n- ⚡ **SSR Compatible** - Fully compatible with Next.js App Router\n- 📦 **TypeScript** - Full TypeScript support with comprehensive types\n\n## Installation\n\n```bash\nnpm install @abdev/next-auth-custom\n```\n\n## Quick Example\n\n```typescript\nimport {\n  createAuthService,\n  createDefaultAuthConfig,\n  JWTUtil,\n  CookieUtil,\n} from '@abdev/next-auth-custom';\n\n// Create configuration\nconst config = createDefaultAuthConfig({\n  jwtSecret: process.env.JWT_SECRET!,\n  jwtRefreshSecret: process.env.JWT_REFRESH_SECRET!,\n});\n\n// Create utilities\nconst jwtUtil = new JWTUtil(config);\nconst cookieUtil = new CookieUtil();\n\n// Use in your API routes\nexport async function POST(request: NextRequest) {\n  const { email, password } = await request.json();\n\n  // Your authentication logic here\n  const user = await authenticateUser(email, password);\n  const tokens = jwtUtil.generateTokenPair(user);\n\n  const response = NextResponse.json({ success: true });\n  cookieUtil.setAuthCookies(response, tokens.accessToken, tokens.refreshToken);\n\n  return response;\n}\n```\n\n## Quick Start\n\n### 1. Environment Variables\n\nAdd these to your `.env.local`:\n\n```env\nJWT_SECRET=your-super-secret-jwt-key\nJWT_REFRESH_SECRET=your-super-secret-refresh-key\n```\n\n### 2. Database Adapter\n\nCreate a database adapter that implements the `DatabaseAdapter` interface:\n\n```typescript\n// lib/database-adapter.ts\nimport { DatabaseAdapter, User } from '@abdev/next-auth-custom';\n\nexport class MyDatabaseAdapter implements DatabaseAdapter {\n  async findUserByEmail(email: string): Promise<User | null> {\n    // Implement your database query\n    const user = await db.users.findUnique({ where: { email } });\n    return user;\n  }\n\n  async findUserById(id: string): Promise<User | null> {\n    const user = await db.users.findUnique({ where: { id } });\n    return user;\n  }\n\n  async createUser(\n    data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>\n  ): Promise<User> {\n    const user = await db.users.create({\n      data: {\n        ...data,\n        id: generateId(), // Implement your ID generation\n        createdAt: new Date(),\n        updatedAt: new Date(),\n      },\n    });\n    return user;\n  }\n\n  async updateUser(id: string, data: Partial<User>): Promise<User> {\n    const user = await db.users.update({\n      where: { id },\n      data: { ...data, updatedAt: new Date() },\n    });\n    return user;\n  }\n\n  async storeRefreshToken(\n    userId: string,\n    token: string,\n    deviceId?: string\n  ): Promise<void> {\n    await db.refreshTokens.create({\n      data: { userId, token, deviceId, createdAt: new Date() },\n    });\n  }\n\n  async validateRefreshToken(token: string, userId: string): Promise<boolean> {\n    const storedToken = await db.refreshTokens.findFirst({\n      where: { token, userId, revoked: false },\n    });\n    return !!storedToken;\n  }\n\n  async revokeRefreshToken(token: string, userId: string): Promise<void> {\n    await db.refreshTokens.updateMany({\n      where: { token, userId },\n      data: { revoked: true, revokedAt: new Date() },\n    });\n  }\n\n  async revokeAllUserTokens(userId: string): Promise<void> {\n    await db.refreshTokens.updateMany({\n      where: { userId },\n      data: { revoked: true, revokedAt: new Date() },\n    });\n  }\n}\n```\n\n### 3. Auth Configuration\n\n```typescript\n// lib/auth-config.ts\nimport { createDefaultAuthConfig } from '@abdev/next-auth-custom';\n\nexport const authConfig = createDefaultAuthConfig({\n  jwtSecret: process.env.JWT_SECRET!,\n  jwtRefreshSecret: process.env.JWT_REFRESH_SECRET!,\n  accessTokenExpiry: 15 * 60, // 15 minutes\n  refreshTokenExpiry: 7 * 24 * 60 * 60, // 7 days\n  enableTokenRotation: true,\n  enableDeviceTracking: false,\n});\n```\n\n### 4. Auth Service\n\n```typescript\n// lib/auth-service.ts\nimport { createAuthService } from '@abdev/next-auth-custom';\nimport { authConfig } from './auth-config';\nimport { MyDatabaseAdapter } from './database-adapter';\n\nconst dbAdapter = new MyDatabaseAdapter();\nexport const authService = createAuthService(authConfig, dbAdapter);\n```\n\n### 5. API Routes\n\n```typescript\n// app/api/auth/login/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { authService } from '@/lib/auth-service';\nimport { CookieUtil } from '@abdev/next-auth-custom';\n\nconst cookieUtil = new CookieUtil();\n\nexport async function POST(request: NextRequest) {\n  try {\n    const { email, password } = await request.json();\n\n    const result = await authService.login({ email, password });\n\n    if (!result.success) {\n      return NextResponse.json({ error: result.error }, { status: 401 });\n    }\n\n    const response = NextResponse.json({\n      user: result.user,\n      success: true,\n    });\n\n    // Set HttpOnly cookies\n    cookieUtil.setAuthCookies(\n      response,\n      result.session!.accessToken,\n      result.session!.refreshToken\n    );\n\n    return response;\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\n```typescript\n// app/api/auth/register/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { authService } from '@/lib/auth-service';\nimport { CookieUtil } from '@abdev/next-auth-custom';\n\nconst cookieUtil = new CookieUtil();\n\nexport async function POST(request: NextRequest) {\n  try {\n    const { email, password, name } = await request.json();\n\n    const result = await authService.register({ email, password, name });\n\n    if (!result.success) {\n      return NextResponse.json({ error: result.error }, { status: 400 });\n    }\n\n    const response = NextResponse.json({\n      user: result.user,\n      success: true,\n    });\n\n    // Set HttpOnly cookies\n    cookieUtil.setAuthCookies(\n      response,\n      result.session!.accessToken,\n      result.session!.refreshToken\n    );\n\n    return response;\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\n```typescript\n// app/api/auth/refresh/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { authService } from '@/lib/auth-service';\nimport { CookieUtil } from '@abdev/next-auth-custom';\n\nconst cookieUtil = new CookieUtil();\n\nexport async function POST(request: NextRequest) {\n  try {\n    const { refreshToken } = cookieUtil.getAuthTokens(request);\n\n    if (!refreshToken) {\n      return NextResponse.json({ error: 'No refresh token' }, { status: 401 });\n    }\n\n    const result = await authService.refresh(refreshToken);\n\n    if (!result.success) {\n      return NextResponse.json({ error: result.error }, { status: 401 });\n    }\n\n    const response = NextResponse.json({\n      user: result.user,\n      success: true,\n    });\n\n    // Set new HttpOnly cookies\n    cookieUtil.setAuthCookies(\n      response,\n      result.session!.accessToken,\n      result.session!.refreshToken\n    );\n\n    return response;\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\n```typescript\n// app/api/auth/logout/route.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { authService } from '@/lib/auth-service';\nimport { CookieUtil } from '@abdev/next-auth-custom';\n\nconst cookieUtil = new CookieUtil();\n\nexport async function POST(request: NextRequest) {\n  try {\n    const { refreshToken } = cookieUtil.getAuthTokens(request);\n\n    if (refreshToken) {\n      await authService.logout(refreshToken);\n    }\n\n    const response = NextResponse.json({ success: true });\n\n    // Clear cookies\n    cookieUtil.clearAuthCookies(response);\n\n    return response;\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\n### 6. Middleware\n\n```typescript\n// middleware.ts\nimport { createAuthMiddleware } from '@abdev/next-auth-custom';\nimport { authConfig } from './lib/auth-config';\nimport { MyDatabaseAdapter } from './lib/database-adapter';\n\nconst dbAdapter = new MyDatabaseAdapter();\n\nconst authMiddleware = createAuthMiddleware({\n  config: authConfig,\n  dbAdapter,\n  middlewareConfig: {\n    protectedRoutes: ['/dashboard', '/profile', '/admin'],\n    publicRoutes: ['/api/public', '/'],\n    authRoutes: ['/api/auth', '/login', '/register'],\n    redirectTo: '/login',\n  },\n});\n\nexport default authMiddleware;\n\nexport const config = {\n  matcher: [\n    /*\n     * Match all request paths except for the ones starting with:\n     * - api (API routes)\n     * - _next/static (static files)\n     * - _next/image (image optimization files)\n     * - favicon.ico (favicon file)\n     */\n    '/((?!api|_next/static|_next/image|favicon.ico).*)',\n  ],\n};\n```\n\n### 7. Client-side Usage\n\n```typescript\n// hooks/useAuth.ts\nimport { useState, useEffect } from 'react';\nimport { User } from '@abdev/next-auth-custom';\n\nexport function useAuth() {\n  const [user, setUser] = useState<User | null>(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    // Fetch current user on mount\n    fetch('/api/auth/me')\n      .then((res) => res.json())\n      .then((data) => {\n        if (data.user) {\n          setUser(data.user);\n        }\n      })\n      .finally(() => setLoading(false));\n  }, []);\n\n  const login = async (email: string, password: string) => {\n    const res = await fetch('/api/auth/login', {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ email, password }),\n    });\n\n    const data = await res.json();\n\n    if (data.success) {\n      setUser(data.user);\n      return { success: true };\n    } else {\n      return { success: false, error: data.error };\n    }\n  };\n\n  const logout = async () => {\n    await fetch('/api/auth/logout', { method: 'POST' });\n    setUser(null);\n  };\n\n  return { user, loading, login, logout };\n}\n```\n\n## API Reference\n\n### Types\n\n#### `User`\n\n```typescript\ninterface User {\n  id: string;\n  email: string;\n  name?: string;\n  avatar?: string;\n  role?: string;\n  createdAt: Date;\n  updatedAt: Date;\n}\n```\n\n#### `AuthConfig`\n\n```typescript\ninterface AuthConfig {\n  jwtSecret: string;\n  jwtRefreshSecret: string;\n  accessTokenExpiry: number; // in seconds\n  refreshTokenExpiry: number; // in seconds\n  cookieDomain?: string;\n  cookieSecure?: boolean;\n  cookieSameSite?: 'strict' | 'lax' | 'none';\n  enableTokenRotation?: boolean;\n  enableDeviceTracking?: boolean;\n}\n```\n\n#### `DatabaseAdapter`\n\n```typescript\ninterface DatabaseAdapter {\n  findUserByEmail(email: string): Promise<User | null>;\n  findUserById(id: string): Promise<User | null>;\n  createUser(data: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User>;\n  updateUser(id: string, data: Partial<User>): Promise<User>;\n  storeRefreshToken(\n    userId: string,\n    token: string,\n    deviceId?: string\n  ): Promise<void>;\n  validateRefreshToken(token: string, userId: string): Promise<boolean>;\n  revokeRefreshToken(token: string, userId: string): Promise<void>;\n  revokeAllUserTokens(userId: string): Promise<void>;\n}\n```\n\n### Classes\n\n#### `AuthServiceImpl`\n\nMain authentication service class.\n\n```typescript\nconst authService = new AuthServiceImpl(config, dbAdapter);\n\n// Methods\nawait authService.register(data: RegisterData): Promise<AuthResponse>\nawait authService.login(credentials: LoginCredentials): Promise<AuthResponse>\nawait authService.refresh(refreshToken: string): Promise<AuthResponse>\nawait authService.logout(refreshToken: string): Promise<AuthResponse>\nawait authService.validateAccessToken(token: string): Promise<User | null>\nawait authService.getUserFromToken(token: string): Promise<User | null>\n```\n\n#### `JWTUtil`\n\nJWT token utility class.\n\n```typescript\nconst jwtUtil = new JWTUtil(config);\n\n// Methods\njwtUtil.generateAccessToken(user: User, deviceId?: string): string\njwtUtil.generateRefreshToken(user: User, deviceId?: string): string\njwtUtil.verifyAccessToken(token: string): JWTPayload | null\njwtUtil.verifyRefreshToken(token: string): JWTPayload | null\njwtUtil.generateTokenPair(user: User, deviceId?: string): TokenResponse\njwtUtil.isTokenExpired(token: string, isRefreshToken?: boolean): boolean\njwtUtil.getTokenExpiration(token: string, isRefreshToken?: boolean): number | null\n```\n\n#### `CookieUtil`\n\nCookie management utility class.\n\n```typescript\nconst cookieUtil = new CookieUtil(options);\n\n// Methods\ncookieUtil.setCookie(response: NextResponse, name: string, value: string, options?: CookieOptions): NextResponse\ncookieUtil.getCookie(request: NextRequest, name: string): string | undefined\ncookieUtil.removeCookie(response: NextResponse, name: string, options?: CookieOptions): NextResponse\ncookieUtil.setAuthCookies(response: NextResponse, accessToken: string, refreshToken: string, options?: CookieOptions): NextResponse\ncookieUtil.clearAuthCookies(response: NextResponse): NextResponse\ncookieUtil.getAuthTokens(request: NextRequest): { accessToken?: string; refreshToken?: string }\n```\n\n### Functions\n\n#### `createAuthService(config, dbAdapter)`\n\nFactory function to create an auth service instance.\n\n#### `createDefaultAuthConfig(overrides)`\n\nHelper function to create default auth configuration.\n\n#### `createAuthMiddleware(options)`\n\nFactory function to create Next.js middleware for route protection.\n\n## Security Features\n\n- **HttpOnly Cookies**: Tokens are stored in HttpOnly cookies to prevent XSS attacks\n- **Token Rotation**: Optional refresh token rotation for enhanced security\n- **Device Tracking**: Optional device-based session management\n- **Secure Headers**: Automatic secure cookie settings in production\n- **Token Expiration**: Configurable token expiration times\n- **Database Validation**: Refresh tokens are validated against database\n\n## Database Schema Examples\n\n### PostgreSQL (with Prisma)\n\n```prisma\nmodel User {\n  id        String   @id @default(cuid())\n  email     String   @unique\n  name      String?\n  role      String   @default(\"user\")\n  createdAt DateTime @default(now())\n  updatedAt DateTime @updatedAt\n\n  refreshTokens RefreshToken[]\n}\n\nmodel RefreshToken {\n  id        String   @id @default(cuid())\n  token     String   @unique\n  userId    String\n  deviceId  String?\n  revoked   Boolean  @default(false)\n  revokedAt DateTime?\n  createdAt DateTime @default(now())\n\n  user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n}\n```\n\n### Firestore\n\n```typescript\n// Collections structure\nusers/{userId} = {\n  email: string,\n  name: string,\n  role: string,\n  createdAt: timestamp,\n  updatedAt: timestamp\n}\n\nrefreshTokens/{tokenId} = {\n  token: string,\n  userId: string,\n  deviceId: string,\n  revoked: boolean,\n  revokedAt: timestamp,\n  createdAt: timestamp\n}\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\nIf you have any questions or need help, please open an issue on GitHub.\n\n# next-auth-custom\n","readmeFilename":"README.md"}