authservice-node
v1.0.0
Published
Node.js/Express SDK for Auth Service - Secure backend authentication and permission verification
Maintainers
Readme
Auth Service Node.js SDK
Secure backend SDK for Node.js/Express applications to integrate with the Auth Service. Provides authentication, authorization, and permission checking with built-in security features.
Features
- 🔐 Secure Request Signing: HMAC-SHA256 request signatures
- ⚡ Built-in Caching: Reduce API calls with intelligent caching
- 🔄 Automatic Retry: Exponential backoff for transient failures
- 🛡️ Express Middleware: Drop-in middleware for permission checks
- 📊 Performance Monitoring: Cache statistics and performance metrics
- 🔍 Type Safety: Full TypeScript support
Installation
npm install @auth-service/node-sdkQuick Start
const { createAuthService } = require('@auth-service/node-sdk');
// Initialize the SDK
const auth = createAuthService({
authServiceUrl: 'https://auth.example.com',
appId: process.env.APP_ID,
appSecret: process.env.APP_SECRET
});
// Use in Express app
const express = require('express');
const app = express();
// Authenticate all routes
app.use(auth.authenticate());
// Protect specific routes
app.delete('/api/posts/:id',
auth.requirePermission('posts:delete'),
async (req, res) => {
// User has permission, proceed with deletion
res.json({ success: true });
}
);Configuration
const auth = createAuthService({
// Required
authServiceUrl: 'https://auth.example.com',
appId: 'your-app-id',
appSecret: 'your-app-secret',
// Optional
cacheEnabled: true, // Enable permission caching (default: true)
cacheTTL: 60, // Cache TTL in seconds (default: 60)
timeout: 10000, // Request timeout in ms (default: 10000)
retryAttempts: 3, // Number of retry attempts (default: 3)
retryDelay: 1000 // Initial retry delay in ms (default: 1000)
});Middleware Usage
Authentication Middleware
Validates user tokens and populates req.user:
// Authenticate all routes
app.use(auth.authenticate());
// Access user info in routes
app.get('/profile', (req, res) => {
res.json({
userId: req.user.id,
permissions: req.user.permissions,
roles: req.user.roles
});
});Permission Middleware
Single Permission
app.post('/api/posts',
auth.requirePermission('posts:create'),
createPostHandler
);Multiple Permissions (ANY)
app.put('/api/posts/:id',
auth.requireAnyPermission(['posts:edit', 'posts:admin']),
updatePostHandler
);Multiple Permissions (ALL)
app.delete('/api/users/:id',
auth.requireAllPermissions(['users:delete', 'admin:access']),
deleteUserHandler
);Middleware Options
app.delete('/api/posts/:id',
auth.requirePermission('posts:delete', {
failureStatusCode: 403, // Custom status code
failureMessage: 'Not authorized to delete posts',
onPermissionDenied: (req, permission) => {
// Log permission denial
console.log(`User ${req.user.id} denied ${permission}`);
}
}),
deletePostHandler
);Direct Client Usage
For more control, use the client directly:
const { client } = auth;
// Get user permissions
const permissions = await client.getUserPermissions(userToken);
// Check single permission
const result = await client.checkPermission({
userToken: token,
permission: 'posts:delete',
context: {
ip: req.ip,
userAgent: req.get('user-agent')
}
});
// Check multiple permissions
const results = await client.checkPermissions({
userToken: token,
permissions: ['posts:create', 'posts:edit', 'posts:delete']
});
// Convenience methods
const hasAny = await client.hasAnyPermission(token, ['admin', 'moderator']);
const hasAll = await client.hasAllPermissions(token, ['posts:read', 'posts:write']);Caching
The SDK includes intelligent caching to reduce API calls:
// Invalidate cache for a user
auth.invalidateCache(userId);
// Clear entire cache
auth.client.clearCache();
// Get cache statistics
const stats = auth.client.getCacheStats();
console.log(`Cache hit rate: ${stats.hitRate * 100}%`);Error Handling
The SDK provides detailed error types:
const { AuthServiceError, TokenError, PermissionError } = require('@auth-service/node-sdk');
app.use(async (req, res, next) => {
try {
await auth.requirePermission('admin:access')(req, res, next);
} catch (error) {
if (error instanceof TokenError) {
// Handle token errors (expired, invalid)
res.status(401).json({ error: 'Invalid token' });
} else if (error instanceof PermissionError) {
// Handle permission errors
res.status(403).json({
error: 'Permission denied',
required: error.details.requiredPermissions
});
} else {
// Handle other errors
res.status(500).json({ error: 'Internal error' });
}
}
});Webhook Verification
Verify webhooks from the Auth Service:
app.post('/webhooks/auth', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
if (!auth.verifyWebhook(signature, timestamp, req.body)) {
return res.status(401).json({ error: 'Invalid webhook signature' });
}
// Process webhook
switch (req.body.event) {
case 'user.created':
// Handle new user
break;
case 'permission.changed':
// Invalidate cache for user
auth.invalidateCache(req.body.userId);
break;
}
res.json({ received: true });
});Advanced Usage
Custom Token Extraction
// The SDK automatically extracts tokens from:
// - Authorization: Bearer <token>
// - X-Access-Token: <token>
// - Query parameter: ?access_token=<token>
// For custom extraction, use the client directly
app.use(async (req, res, next) => {
const token = req.cookies.authToken; // Custom extraction
if (token) {
const permissions = await auth.client.getUserPermissions(token);
req.user = { permissions };
}
next();
});Permission Guards in Route Handlers
const canDelete = auth.middleware.createPermissionGuard('posts:delete');
app.delete('/api/posts/:id', auth.authenticate(), async (req, res) => {
// Check permission inside handler
if (!await canDelete(req)) {
return res.status(403).json({ error: 'Cannot delete post' });
}
// Additional business logic checks
const post = await getPost(req.params.id);
if (post.authorId !== req.user.id && !await canDelete(req)) {
return res.status(403).json({ error: 'Not your post' });
}
// Delete the post
await deletePost(req.params.id);
res.json({ success: true });
});Conditional Middleware
// Optional permission check - doesn't block request
app.get('/api/posts',
auth.middleware.checkPermission('posts:admin'),
async (req, res) => {
const isAdmin = req.authContext?.hasPermission?.['posts:admin'];
// Return different data based on permissions
const posts = isAdmin
? await getAllPosts()
: await getPublicPosts();
res.json(posts);
}
);Best Practices
- Always verify permissions server-side - Never trust client-side checks
- Use caching wisely - Balance between performance and data freshness
- Handle errors gracefully - Provide clear error messages
- Monitor performance - Use cache statistics to optimize
- Secure your credentials - Never expose appSecret in client code
- Use specific permissions - Prefer
posts:deleteover genericadmin
Troubleshooting
Common Issues
"Invalid signature" errors
- Ensure your system clock is synchronized
- Verify appId and appSecret are correct
- Check that authServiceUrl doesn't have trailing slash
Cache not working
- Verify cacheEnabled is true
- Check cache statistics with getCacheStats()
- Ensure unique user IDs in tokens
Permissions always denied
- Verify token includes correct appId
- Check user has required roles assigned
- Ensure permissions exist in the system
Debug Mode
Enable debug logging:
const auth = createAuthService({
// ... config
debug: true // Logs all API calls and cache operations
});TypeScript Support
Full TypeScript support with type definitions:
import { createAuthService, AuthenticatedRequest } from '@auth-service/node-sdk';
const auth = createAuthService({
authServiceUrl: process.env.AUTH_SERVICE_URL!,
appId: process.env.APP_ID!,
appSecret: process.env.APP_SECRET!
});
app.get('/profile', auth.authenticate(), (req: AuthenticatedRequest, res) => {
// req.user is fully typed
res.json({
id: req.user!.id,
permissions: req.user!.permissions
});
});License
MIT
