Node.js client library for ldauth permission checking
npm install @ldauth/nodeNode.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.
- 🔐 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
``bash`
npm install @ldauth/nodeor
yarn add @ldauth/nodeor
pnpm add @ldauth/node
`typescript
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: 'user@example.com', groups: ['admin'] }
`
`typescript
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 };
});
`
`typescript
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());
`
`typescript
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.
`typescript
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
}
`
- LDAUTH_API_URL - Set the API URL via environment variable instead of config
The library provides specific error classes for different scenarios:
`typescript
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);
}
}
`
`typescript`
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);
}
});
`typescript
// 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;
}
});
`
`typescript`
// Check permission for a specific resource instance
await ldauth.checkPermission(token, {
resource: 'posts',
action: 'edit',
instanceId: 'post-123' // Check for this specific post
});
`typescript`
// Override default schema and profile
await ldauth.checkPermission(token, {
resource: 'admin',
action: 'access',
schema: 'admin-portal',
profile: 'elevated'
});
#### 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.
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() configurationpermissions
- - 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
MIT