{"_id":"@abstraks-dev/jwt-auth","name":"@abstraks-dev/jwt-auth","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@abstraks-dev/jwt-auth","version":"1.0.0","description":"JWT token generation and validation with AWS Secrets Manager integration for Lambda microservices","type":"module","main":"src/index.js","exports":{".":"./src/index.js"},"scripts":{"test":"NODE_OPTIONS=--experimental-vm-modules jest"},"keywords":["jwt","authentication","aws-lambda","secrets-manager","jsonwebtoken","token-validation","microservices","abstraks"],"author":{"name":"Abstraks"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/Abstraks-co/shared-modules.git","directory":"packages/jwt-auth"},"bugs":{"url":"https://github.com/Abstraks-co/shared-modules/issues"},"homepage":"https://github.com/Abstraks-co/shared-modules/tree/main/packages/jwt-auth#readme","dependencies":{"jsonwebtoken":"^9.0.2"},"peerDependencies":{"@aws-sdk/client-secrets-manager":"^3.0.0"},"peerDependenciesMeta":{"@aws-sdk/client-secrets-manager":{"optional":true}},"_id":"@abstraks-dev/jwt-auth@1.0.0","gitHead":"f629da8e3ebc2d8fdc6f83a147ea8e50b8bc66b5","_nodeVersion":"20.19.5","_npmVersion":"10.8.2","dist":{"integrity":"sha512-LpJHiMFV8uAlLsh75EqmgtqXp6O/YGIBXL6tmScqECJSZhqiuzz+4w6kaEsnJz0gEHF49EY6EsafFm8bR7NYpQ==","shasum":"4c77bfbd9e82c560d7fc83121db6226972022a48","tarball":"https://registry.npmjs.org/@abstraks-dev/jwt-auth/-/jwt-auth-1.0.0.tgz","fileCount":5,"unpackedSize":34114,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDoD91ygFpaE6sy9lnB0ju7zfjdpitIKOgAT/owpIBg/AiBm47l25I3AEgogWySDUHwTTvcOiWN3qbtBnPYoBcltqA=="}]},"_npmUser":{"name":"abstraks-dev","email":"contactabstraks@gmail.com"},"directories":{},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/jwt-auth_1.0.0_1763675401113_0.8337046829444321"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-20T21:50:01.016Z","1.0.0":"2025-11-20T21:50:01.306Z","modified":"2025-11-20T21:50:01.621Z"},"maintainers":[{"name":"abstraks-dev","email":"contactabstraks@gmail.com"}],"description":"JWT token generation and validation with AWS Secrets Manager integration for Lambda microservices","homepage":"https://github.com/Abstraks-co/shared-modules/tree/main/packages/jwt-auth#readme","keywords":["jwt","authentication","aws-lambda","secrets-manager","jsonwebtoken","token-validation","microservices","abstraks"],"repository":{"type":"git","url":"git+https://github.com/Abstraks-co/shared-modules.git","directory":"packages/jwt-auth"},"author":{"name":"Abstraks"},"bugs":{"url":"https://github.com/Abstraks-co/shared-modules/issues"},"license":"MIT","readme":"# @abstraks-dev/jwt-auth\n\nJWT token generation and validation with AWS Secrets Manager integration.\n\n## Features\n\n- 🔐 **JWT Token Management**: Generate and verify JSON Web Tokens\n- ☁️ **AWS Integration**: Optional Secrets Manager for production environments\n- ⚡ **Secret Caching**: 5-minute cache reduces AWS API calls\n- 🛡️ **Middleware Support**: Lambda handler wrapper for automatic authentication\n- 🎯 **Flexible Configuration**: Direct secrets, custom getSecret functions, or Secrets Manager\n- ⏰ **Configurable Expiration**: Default 364 days, customizable per token\n- 🚨 **Comprehensive Error Handling**: Standardized error responses with status codes\n\n## Installation\n\n```bash\nnpm install @abstraks-dev/jwt-auth\n```\n\n### Optional Peer Dependencies\n\nFor AWS Secrets Manager integration:\n\n```bash\nnpm install @aws-sdk/client-secrets-manager\n```\n\n## Usage\n\n### Simple Usage (Direct Secret)\n\n```javascript\nimport { createSimpleAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = createSimpleAuthenticator(process.env.JWT_SECRET);\n\n// Generate token\nconst token = await auth.generateToken({ _id: 'user123' });\n\n// Verify token\nconst decoded = await auth.verifyToken(token);\nconsole.log(decoded._id); // 'user123'\n```\n\n### Lambda Handler with Middleware\n\n```javascript\nimport { createSimpleAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = createSimpleAuthenticator(process.env.JWT_SECRET);\n\nconst handler = async (event) => {\n\t// event.auth contains { userId, decoded }\n\tconst userId = event.auth.userId;\n\n\treturn {\n\t\tstatusCode: 200,\n\t\tbody: JSON.stringify({ message: `Hello ${userId}` }),\n\t};\n};\n\nexport const authenticatedHandler = auth.withAuth(handler);\n```\n\n### AWS Secrets Manager Integration\n\n```javascript\nimport { createSecretsManagerAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = await createSecretsManagerAuthenticator(\n\t'auth-prod', // Secret name\n\t'JWT_SECRET', // Key within secret\n\t'us-west-2' // Region\n);\n\nconst token = await auth.generateToken({ _id: 'user123' });\n```\n\n### Custom getSecret Function\n\n```javascript\nimport { createJWTAuthenticator } from '@abstraks-dev/jwt-auth';\nimport { getSecret } from './my-secret-manager.js';\n\nconst auth = createJWTAuthenticator({\n\tgetSecret,\n\tsecretName: 'auth-prod',\n\tsecretKey: 'JWT_SECRET',\n});\n\nconst token = await auth.generateToken({ _id: 'user123' });\n```\n\n### Lambda Event Authentication\n\n```javascript\nimport { createSimpleAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = createSimpleAuthenticator(process.env.JWT_SECRET);\n\nexport const handler = async (event) => {\n\ttry {\n\t\t// Extracts token from Authorization header\n\t\tconst { userId, decoded } = await auth.authenticateEvent(event);\n\n\t\treturn {\n\t\t\tstatusCode: 200,\n\t\t\tbody: JSON.stringify({ userId, claims: decoded }),\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\tstatusCode: error.statusCode || 500,\n\t\t\tbody: JSON.stringify({ error: error.message }),\n\t\t};\n\t}\n};\n```\n\n## API Documentation\n\n### `createSimpleAuthenticator(jwtSecret)`\n\nCreates an authenticator with a direct JWT secret.\n\n**Parameters:**\n\n- `jwtSecret` (string, required): The JWT secret key\n\n**Returns:** Authenticator instance\n\n**Example:**\n\n```javascript\nconst auth = createSimpleAuthenticator('my-secret-key-12345');\n```\n\n---\n\n### `createJWTAuthenticator(options)`\n\nCreates an authenticator with flexible secret management.\n\n**Parameters:**\n\n- `options.getSecret` (function): Async function `(secretName, secretKey) => string`\n- `options.secretName` (string): Name of secret to fetch\n- `options.secretKey` (string, optional): Key within secret (if JSON)\n- `options.jwtSecret` (string): Direct JWT secret (alternative to getSecret)\n\n**Returns:** Authenticator instance\n\n**Example:**\n\n```javascript\nconst auth = createJWTAuthenticator({\n\tgetSecret: async (name, key) => {\n\t\t// Your custom secret fetching logic\n\t\treturn mySecretManager.get(name, key);\n\t},\n\tsecretName: 'auth-prod',\n\tsecretKey: 'JWT_SECRET',\n});\n```\n\n---\n\n### `createSecretsManagerAuthenticator(secretName, secretKey, region)`\n\nCreates an authenticator using AWS Secrets Manager.\n\n**Parameters:**\n\n- `secretName` (string, required): AWS Secrets Manager secret name\n- `secretKey` (string, optional): Key within JSON secret\n- `region` (string, optional): AWS region (defaults to AWS_REGION env var)\n\n**Returns:** Promise<Authenticator>\n\n**Example:**\n\n```javascript\nconst auth = await createSecretsManagerAuthenticator(\n\t'auth-prod',\n\t'JWT_SECRET',\n\t'us-west-2'\n);\n```\n\n---\n\n### Authenticator Methods\n\n#### `generateToken(payload, options)`\n\nGenerates a signed JWT token.\n\n**Parameters:**\n\n- `payload` (object, required): Token payload (typically `{ _id: 'userId' }`)\n- `options.expiresIn` (string, optional): Expiration time (default: '364d')\n- `options.additionalClaims` (object, optional): Additional JWT claims\n\n**Returns:** Promise<string> - Signed JWT token\n\n**Example:**\n\n```javascript\nconst token = await auth.generateToken(\n\t{ _id: 'user123' },\n\t{\n\t\texpiresIn: '7d',\n\t\tadditionalClaims: { role: 'admin', scope: 'all' },\n\t}\n);\n```\n\n---\n\n#### `verifyToken(token, options)`\n\nVerifies and decodes a JWT token.\n\n**Parameters:**\n\n- `token` (string, required): JWT token to verify\n- `options.requireUserId` (boolean, optional): Require `_id` in payload (default: true)\n\n**Returns:** Promise<object> - Decoded token payload\n\n**Throws:**\n\n- Error with `statusCode: 401` for invalid/expired tokens\n- Error messages: 'Token is required', 'Invalid token', 'Token has expired', 'Token payload invalid: user ID not found'\n\n**Example:**\n\n```javascript\ntry {\n\tconst decoded = await auth.verifyToken(token);\n\tconsole.log(decoded._id, decoded.role);\n} catch (error) {\n\tconsole.error(error.message); // 'Token has expired'\n\tconsole.error(error.statusCode); // 401\n}\n```\n\n---\n\n#### `authenticateEvent(event, options)`\n\nExtracts and verifies token from Lambda event.\n\n**Parameters:**\n\n- `event` (object, required): Lambda event with headers\n- `options.headerName` (string, optional): Header name (default: 'Authorization')\n- `options.requireUserId` (boolean, optional): Require `_id` in payload (default: true)\n\n**Returns:** Promise<object> - `{ userId: string, decoded: object }`\n\n**Throws:**\n\n- Error with `statusCode: 401` if token missing or invalid\n\n**Example:**\n\n```javascript\n// Handles both formats:\n// Authorization: Bearer eyJhbGc...\n// Authorization: eyJhbGc...\n\nconst { userId, decoded } = await auth.authenticateEvent(event);\n\n// Custom header\nconst result = await auth.authenticateEvent(event, {\n\theaderName: 'X-Auth-Token',\n});\n```\n\n---\n\n#### `withAuth(handler, options)`\n\nMiddleware wrapper for Lambda handlers.\n\n**Parameters:**\n\n- `handler` (function, required): Async Lambda handler function\n- `options.headerName` (string, optional): Header name (default: 'Authorization')\n- `options.requireUserId` (boolean, optional): Require `_id` in payload (default: true)\n\n**Returns:** Wrapped handler function\n\n**Behavior:**\n\n- Authenticates request before calling handler\n- Attaches `event.auth = { userId, decoded }` to event\n- Returns standardized 401 response on auth failure\n- CORS headers included in error responses\n\n**Example:**\n\n```javascript\nconst protectedHandler = async (event) => {\n\tconst userId = event.auth.userId;\n\tconst role = event.auth.decoded.role;\n\n\t// Your handler logic\n\treturn {\n\t\tstatusCode: 200,\n\t\tbody: JSON.stringify({ userId, role }),\n\t};\n};\n\nexport const handler = auth.withAuth(protectedHandler);\n```\n\n## Secret Caching\n\nThe authenticator caches secrets for **5 minutes** to reduce AWS API calls:\n\n```javascript\nconst auth = await createSecretsManagerAuthenticator('auth-prod');\n\nawait auth.generateToken({ _id: 'user1' }); // Fetches secret\nawait auth.generateToken({ _id: 'user2' }); // Uses cache\nawait auth.generateToken({ _id: 'user3' }); // Uses cache\n\n// After 5 minutes, next call fetches fresh secret\n```\n\n**Benefits:**\n\n- Reduces AWS Secrets Manager costs\n- Improves Lambda cold start performance\n- Minimizes latency on subsequent requests\n\n**Considerations:**\n\n- Secret rotation takes up to 5 minutes to propagate\n- Lambda container reuse extends effective cache lifetime\n- Each container instance has independent cache\n\n## Error Handling\n\nAll authentication errors include a `statusCode` property for easy HTTP response mapping:\n\n```javascript\ntry {\n\tconst decoded = await auth.verifyToken(expiredToken);\n} catch (error) {\n\tconsole.error(error.message); // 'Token has expired'\n\tconsole.error(error.statusCode); // 401\n\n\treturn {\n\t\tstatusCode: error.statusCode || 500,\n\t\tbody: JSON.stringify({ error: error.message }),\n\t};\n}\n```\n\n**Error Types:**\n\n- `Token is required` - Missing token (401)\n- `Authorization token is required` - Missing header in authenticateEvent (401)\n- `Invalid token` - Malformed JWT (401)\n- `Token has expired` - Expired token (401)\n- `Token payload invalid: user ID not found` - Missing `_id` when required (401)\n- `JWT secret not configured` - Configuration error (500)\n\n## Migration Guide\n\n### From Auth Service Pattern\n\n**Before (Auth service):**\n\n```javascript\nimport { getSecret } from '../helpers/secretsManager.js';\nimport jwt from 'jsonwebtoken';\n\nconst generateJWTToken = async (userId) => {\n\tconst jwtSecret = await getSecret(process.env.JWT_SECRET_NAME, 'JWT_SECRET');\n\treturn jwt.sign({ _id: userId }, jwtSecret, { expiresIn: '364d' });\n};\n\nexport const authenticateEvent = async (event) => {\n\tconst token =\n\t\tevent.headers['Authorization'] || event.headers['authorization'];\n\tif (!token) throw new Error('Authorization token is required');\n\n\tconst jwtSecret = await getSecret(process.env.JWT_SECRET_NAME, 'JWT_SECRET');\n\tconst decoded = jwt.verify(token.replace('Bearer ', ''), jwtSecret);\n\n\tif (!decoded._id) throw new Error('Invalid token payload');\n\treturn { userId: decoded._id, decoded };\n};\n```\n\n**After (with @abstraks-dev/jwt-auth):**\n\n```javascript\nimport { createSecretsManagerAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = await createSecretsManagerAuthenticator(\n\tprocess.env.JWT_SECRET_NAME,\n\t'JWT_SECRET',\n\tprocess.env.AWS_REGION\n);\n\n// Generate token\nconst token = await auth.generateToken({ _id: userId });\n\n// Authenticate event\nexport const handler = auth.withAuth(async (event) => {\n\tconst userId = event.auth.userId;\n\t// Your logic here\n});\n```\n\n**Benefits:**\n\n- ✅ Automatic secret caching (5 minutes)\n- ✅ Standardized error handling with status codes\n- ✅ Middleware pattern reduces boilerplate\n- ✅ Case-insensitive header matching\n- ✅ Handles both Bearer and direct token formats\n- ✅ Comprehensive error messages\n\n### From Social Service Pattern\n\n**Before (Social service):**\n\n```javascript\nconst verifyToken = async (token) => {\n\ttry {\n\t\tconst jwtSecret = await getSecret(secretName, 'JWT_SECRET');\n\t\treturn jwt.verify(token, jwtSecret);\n\t} catch (error) {\n\t\tif (error.name === 'TokenExpiredError') {\n\t\t\tthrow new Error('Token has expired');\n\t\t}\n\t\tthrow new Error('Invalid token');\n\t}\n};\n```\n\n**After:**\n\n```javascript\nimport { createSecretsManagerAuthenticator } from '@abstraks-dev/jwt-auth';\n\nconst auth = await createSecretsManagerAuthenticator(secretName, 'JWT_SECRET');\n\ntry {\n\tconst decoded = await auth.verifyToken(token);\n} catch (error) {\n\t// error.message: 'Token has expired' or 'Invalid token'\n\t// error.statusCode: 401\n}\n```\n\n## Best Practices\n\n### 1. Use Secrets Manager in Production\n\n```javascript\n// Development\nconst auth = createSimpleAuthenticator(process.env.JWT_SECRET);\n\n// Production\nconst auth = await createSecretsManagerAuthenticator(\n\tprocess.env.JWT_SECRET_NAME,\n\t'JWT_SECRET'\n);\n```\n\n### 2. Use Middleware for Protected Routes\n\n```javascript\n// Good: Middleware handles auth automatically\nexport const handler = auth.withAuth(async (event) => {\n\tconst userId = event.auth.userId;\n\t// Your logic\n});\n\n// Avoid: Manual authentication in every handler\nexport const handler = async (event) => {\n\ttry {\n\t\tconst { userId } = await auth.authenticateEvent(event);\n\t\t// Your logic\n\t} catch (error) {\n\t\treturn {\n\t\t\tstatusCode: 401,\n\t\t\tbody: JSON.stringify({ error: error.message }),\n\t\t};\n\t}\n};\n```\n\n### 3. Set Appropriate Expiration\n\n```javascript\n// Long-lived user sessions\nconst sessionToken = await auth.generateToken(\n\t{ _id: userId },\n\t{ expiresIn: '364d' }\n);\n\n// Short-lived API tokens\nconst apiToken = await auth.generateToken(\n\t{ _id: userId, scope: 'api' },\n\t{ expiresIn: '1h' }\n);\n\n// Refresh tokens\nconst refreshToken = await auth.generateToken(\n\t{ _id: userId, type: 'refresh' },\n\t{ expiresIn: '30d' }\n);\n```\n\n### 4. Include Role/Scope in Additional Claims\n\n```javascript\nconst token = await auth.generateToken(\n\t{ _id: userId },\n\t{\n\t\tadditionalClaims: {\n\t\t\trole: user.role,\n\t\t\tpermissions: user.permissions,\n\t\t\torganizationId: user.organizationId,\n\t\t},\n\t}\n);\n\n// Later in handler\nconst { decoded } = event.auth;\nif (decoded.role !== 'admin') {\n\treturn { statusCode: 403, body: JSON.stringify({ error: 'Forbidden' }) };\n}\n```\n\n### 5. Handle Errors Gracefully\n\n```javascript\nexport const handler = auth.withAuth(\n\tasync (event) => {\n\t\t// Your logic\n\t},\n\t{\n\t\t// Custom error handler\n\t\tonError: (error) => ({\n\t\t\tstatusCode: error.statusCode || 500,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t'Access-Control-Allow-Origin': '*',\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\terror: error.message,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t}),\n\t\t}),\n\t}\n);\n```\n\n## Troubleshooting\n\n### \"JWT secret not configured\"\n\n**Cause:** No secret provided to authenticator.\n\n**Solution:**\n\n```javascript\n// Ensure one of these is configured:\ncreateJWTAuthenticator({ jwtSecret: 'secret' });\ncreateJWTAuthenticator({ getSecret, secretName: 'name' });\nawait createSecretsManagerAuthenticator('name');\n```\n\n### \"Authorization token is required\"\n\n**Cause:** Missing Authorization header in request.\n\n**Solution:**\n\n```javascript\n// Ensure header is present (case-insensitive):\nheaders: {\n\tAuthorization: 'Bearer <token>';\n\t// OR\n\tauthorization: '<token>';\n}\n```\n\n### \"Token has expired\"\n\n**Cause:** Token expiration time has passed.\n\n**Solution:** Generate new token or increase expiration:\n\n```javascript\nconst token = await auth.generateToken(\n\t{ _id: userId },\n\t{ expiresIn: '7d' } // Increase from default\n);\n```\n\n### AWS Secrets Manager Errors\n\n**Issue:** `AccessDeniedException` or timeout errors.\n\n**Solution:**\n\n1. Verify Lambda has `secretsmanager:GetSecretValue` permission\n2. Check secret exists in correct region\n3. Verify VPC configuration if using private subnets\n4. Add error handling for transient failures:\n\n```javascript\ntry {\n\tconst auth = await createSecretsManagerAuthenticator('auth-prod');\n} catch (error) {\n\tconsole.error('Failed to initialize authenticator:', error);\n\t// Fallback or retry logic\n}\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-759eed91e0e3d23992f77b30ed9a800c"}