@ldauth/node
v0.10.1
Published
Node.js client library for ldauth permission checking
Maintainers
Readme
@ldauth/node
Node.js client library for ldauth permission checking. This library provides a simple way to integrate permission checking into your Node.js applications using the ldauth authentication and authorization service.
Features
- 🔐 Simple permission checking against ldauth API
- ⚡ Built-in caching for improved performance
- 🎯 Framework integrations for Fastify, Express, and tRPC
- 🚀 Automatic batching for tRPC to minimize API calls
- 🔍 Optional local JWT validation
- 💪 TypeScript support with full type definitions
- 🛡️ Comprehensive error handling
Installation
npm install @ldauth/node
# or
yarn add @ldauth/node
# or
pnpm add @ldauth/nodeQuick Start
Basic Usage
import { LDAuthClient } from '@ldauth/node';
const ldauth = new LDAuthClient({
apiUrl: 'https://auth.example.com',
defaultSchema: 'myapp',
cacheTimeSeconds: 5 // Cache results for 5 seconds
});
// Check a permission
try {
await ldauth.checkPermission(token, {
resource: 'users',
action: 'read'
});
console.log('Permission granted!');
} catch (error) {
console.error('Permission denied');
}
// Get all user permissions
const permissions = await ldauth.getUserPermissions(token);
console.log(permissions);
// { permissions: { users: ['read', 'write'], posts: ['read'] } }
// Get user info
const userInfo = await ldauth.getUserInfo(token);
console.log(userInfo);
// { id: '123', email: '[email protected]', groups: ['admin'] }Fastify Integration
import fastify from 'fastify';
import { ldauthFastify } from '@ldauth/node/fastify';
const app = fastify();
// Register the plugin
await app.register(ldauthFastify, {
apiUrl: 'https://auth.example.com',
defaultSchema: 'myapp',
validateTokenLocally: true, // Validate JWT before calling API
cacheTimeSeconds: 5
});
// Simple permission check
app.get('/users', {
preHandler: app.ldauth.require('users', 'read')
}, async (request, reply) => {
return { users: [] };
});
// With user info populated
app.get('/profile', {
preHandler: [
app.ldauth.require('profile', 'view'),
app.ldauth.populateUser()
]
}, async (request, reply) => {
return {
email: request.user.email,
groups: request.user.groups
};
});
// Check multiple permissions (AND)
app.post('/admin', {
preHandler: app.ldauth.requireAll([
['admin', 'access'],
['settings', 'write']
])
}, async (request, reply) => {
return { success: true };
});
// Check multiple permissions (OR)
app.put('/content', {
preHandler: app.ldauth.requireAny([
['content', 'edit'],
['admin', 'access']
])
}, async (request, reply) => {
return { success: true };
});Express Integration
import express from 'express';
import { ldauthExpress } from '@ldauth/node/express';
const app = express();
// Create middleware factory
const ldauth = ldauthExpress({
apiUrl: 'https://auth.example.com',
defaultSchema: 'myapp',
validateTokenLocally: true,
cacheTimeSeconds: 5
});
// Simple permission check
app.get('/users',
ldauth.require('users', 'read'),
(req, res) => {
res.json({ users: [] });
}
);
// With user info populated
app.get('/profile',
ldauth.require('profile', 'view'),
ldauth.populateUser(),
(req, res) => {
res.json({
email: req.user.email,
groups: req.user.groups
});
}
);
// Check multiple permissions (AND)
app.post('/admin',
ldauth.requireAll([
['admin', 'access'],
['settings', 'write']
]),
(req, res) => {
res.json({ success: true });
}
);
// Check multiple permissions (OR)
app.put('/content',
ldauth.requireAny([
['content', 'edit'],
['admin', 'access']
]),
(req, res) => {
res.json({ success: true });
}
);
// Global middleware for all /api routes
app.use('/api', ldauth.populateUser());tRPC Integration
import { initTRPC } from '@trpc/server';
import { createLDAuthMiddleware, PermissionBatcher } from '@ldauth/node/trpc';
import { LDAuthClient } from '@ldauth/node';
// Initialize tRPC
const t = initTRPC.context().create();
// Create LDAuth client and batcher for automatic batching
const ldauthClient = new LDAuthClient({
apiUrl: 'https://auth.example.com',
audience: 'https://api.example.com',
defaultSchemaName: 'myapp',
cacheTimeSeconds: 5
});
const batcher = new PermissionBatcher(ldauthClient);
// Create the middleware
const authMiddleware = createLDAuthMiddleware({
client: batcher,
defaultSchemaName: 'myapp'
});
// Create a protected procedure
const protectedProcedure = t.procedure.use(authMiddleware);
// Define your router
export const appRouter = t.router({
// Simple permission check
getUsers: protectedProcedure
.meta({
permissions: {
resource: 'users',
action: 'read'
}
})
.query(async ({ ctx }) => {
// Permission automatically checked
// Token available in ctx.token
return { users: [] };
}),
// Require all permissions (AND logic)
adminAction: protectedProcedure
.meta({
requireAll: [
{ resource: 'admin', action: 'access' },
{ resource: 'settings', action: 'write' }
]
})
.mutation(async ({ ctx, input }) => {
return { success: true };
}),
// Require any permission (OR logic)
editContent: protectedProcedure
.meta({
requireAny: [
{ resource: 'content', action: 'edit' },
{ resource: 'admin', action: 'access' }
]
})
.mutation(async ({ ctx, input }) => {
return { success: true };
}),
// Object-level permission
editPost: protectedProcedure
.meta({
permissions: {
resource: 'posts',
action: 'edit',
instanceId: 'post-123' // Check for specific post
}
})
.mutation(async ({ ctx, input }) => {
return { success: true };
}),
// No permissions required (public endpoint)
getPublicData: t.procedure
.query(async () => {
return { data: 'public' };
})
});
export type AppRouter = typeof appRouter;Note: When tRPC batches multiple procedures in one HTTP request, all permission checks are automatically batched into one or more API calls (grouped by token), significantly improving performance.
Configuration
Client Options
interface LDAuthClientConfig {
// Base URL of your ldauth API gateway
apiUrl?: string; // Can use LDAUTH_API_URL env var
// Default schema for permission checks
defaultSchema?: string;
// Default profile (defaults to '__default')
defaultProfile?: string;
// Validate JWT locally before API call
validateTokenLocally?: boolean; // Default: false
// Cache results for this many seconds
cacheTimeSeconds?: number; // Default: 0 (no cache)
// API request timeout in milliseconds
timeout?: number; // Default: 10000
// Custom headers for API requests
headers?: Record<string, string>;
}Environment Variables
LDAUTH_API_URL- Set the API URL via environment variable instead of config
Error Handling
The library provides specific error classes for different scenarios:
import {
LDAuthPermissionError,
LDAuthTokenError,
LDAuthAPIError,
LDAuthConfigError
} from '@ldauth/node';
try {
await ldauth.checkPermission(token, {
resource: 'users',
action: 'delete'
});
} catch (error) {
if (error instanceof LDAuthPermissionError) {
// Permission was denied
console.log('Required permission:', error.requiredPermission);
} else if (error instanceof LDAuthTokenError) {
// Token is invalid or expired
console.log('Token error:', error.message);
} else if (error instanceof LDAuthAPIError) {
// API communication failed
console.log('API error:', error.statusCode, error.message);
}
}Express Error Handler
app.use((err, req, res, next) => {
if (err.name === 'LDAuthPermissionError') {
res.status(403).json({
error: 'Permission denied',
required: err.requiredPermission
});
} else if (err.name === 'LDAuthTokenError') {
res.status(401).json({
error: 'Invalid token'
});
} else {
next(err);
}
});Advanced Usage
Custom Token Extraction
// Fastify
await app.register(ldauthFastify, {
apiUrl: 'https://auth.example.com',
extractToken: (request) => {
// Get token from custom header or cookie
return request.headers['x-auth-token'] ||
request.cookies?.authToken;
}
});
// Express
const ldauth = ldauthExpress({
apiUrl: 'https://auth.example.com',
extractToken: (req) => {
// Get token from custom header or cookie
return req.headers['x-auth-token'] ||
req.cookies?.authToken;
}
});Object-Level Permissions
// Check permission for a specific resource instance
await ldauth.checkPermission(token, {
resource: 'posts',
action: 'edit',
instanceId: 'post-123' // Check for this specific post
});Custom Schema and Profile
// Override default schema and profile
await ldauth.checkPermission(token, {
resource: 'admin',
action: 'access',
schema: 'admin-portal',
profile: 'elevated'
});API Reference
LDAuthClient
checkPermission(token, options)
Check if the user has a specific permission.
getUserPermissions(token, options?)
Get all permissions for the user.
getUserInfo(token)
Get user information from the token.
getClientIdFromToken(token)
Extract client ID from token without validation.
clearCache()
Clear all cached results.
Framework Integrations
Fastify and Express integrations provide:
require(resource, action, options?)- Require a single permissionrequireAll(permissions)- Require all specified permissionsrequireAny(permissions)- Require at least one permissionpopulateUser()- Add user info to requestpopulatePermissions(options?)- Add all permissions to request
tRPC integration provides:
- Permission checking via procedure
.meta()configuration permissions- Single permission requirementrequireAll- Require all specified permissions (AND logic)requireAny- Require any of the specified permissions (OR logic)- Automatic batching of permission checks across procedures
- Token automatically added to context after successful validation
License
MIT
