@science-and-humans/science-and-humans-auth-sdk
v2.0.1
Published
Authentication sdk for Science and Humans microservices with hybrid role system support
Downloads
195
Readme
Auth Middleware SDK
A lightweight, production-ready authentication middleware for NestJS applications with Clerk integration and Redis fallback support.
System Design
Auth Design

SDK Design

Features
- 🔐 Clerk Authentication: Full integration with Clerk authentication service
- 🚀 NestJS Native: Built specifically for NestJS with guards, decorators, and modules
- ⚡ Express Support: Drop-in middleware and helpers for Express apps
- 🔄 Redis Fallback: Automatic fallback from Redis to HTTP when microservice is unavailable
- 🛡️ Smart Error Handling: Distinguishes between authentication and infrastructure errors
- 📦 Type Safety: Full TypeScript support with comprehensive type definitions
- 🎯 Role-Based Access: Support for roles and permissions with decorators
- 🏢 Organization Authorization: Advanced organization-level role and permission management
- 📊 Health Monitoring: Built-in health checks and service monitoring
Installation
npm install @science-and-humans/science-and-humans-auth-sdkQuick Start
1. Module Configuration
import { Module } from '@nestjs/common';
import { AuthMiddlewareModule } from '@science-and-humans/science-and-humans-auth-sdk';
@Module({
imports: [
AuthMiddlewareModule.forRoot({
authServiceUrl: 'http://localhost:3000',
serviceKey: 'your-service-key',
serviceName: 'your-service-name',
redis: {
host: 'localhost',
port: 6379,
password: 'your-redis-password', // optional
db: 0,
},
requestTimeout: 5000, // optional, default: 8000ms
}),
],
})
export class AppModule {}2. Using Guards and Decorators
import { Controller, Get, UseGuards } from '@nestjs/common';
import {
AuthGuard,
RolesGuard,
RequireRoles,
RequireOrgRole,
CurrentUser,
UserRoles,
GetUserRoleInfo,
Public,
ClerkUser,
UserRoleInfo,
} from '@science-and-humans/science-and-humans-auth-sdk';
@Controller('api')
@UseGuards(AuthGuard, RolesGuard)
export class ApiController {
@Get('public')
@Public() // Skip authentication
getPublicData() {
return { message: 'Public endpoint' };
}
@Get('protected')
getProtectedData(@CurrentUser() user: ClerkUser) {
return { message: 'Protected endpoint', user };
}
@Get('admin')
@RequireRoles('admin', 'moderator')
getAdminData(@CurrentUser() user: ClerkUser, @UserRoles() roles: string[]) {
return { message: 'Admin only', user, roles };
}
@Get('organization/dashboard')
@RequireOrgRole('org:admin', 'org:moderator')
getOrgDashboard(
@CurrentUser() user: ClerkUser,
@GetUserRoleInfo() roleInfo: UserRoleInfo
) {
return {
message: 'Organization dashboard',
user: user.id,
organizationId: roleInfo.organizationId,
orgRole: roleInfo.orgRole,
};
}
}AuthGuard Behavior
The AuthGuard now works in an opt-in manner for better scalability:
- By default: Routes are accessible without authentication
- With
@RequireAuth(): Routes require authentication - With
@Public(): Routes are explicitly marked as public (overrides@RequireAuth())
Usage Examples
import {
AuthGuard,
RequireAuth,
Public,
RequireRoles,
RequirePermissions
} from '@science-and-humans/science-and-humans-auth-sdk';
@Controller('api')
@UseGuards(AuthGuard) // Apply guard to all routes in controller
export class ApiController {
@Get('public')
@Public() // Explicitly mark as public (overrides guard)
getPublicData() {
return { message: 'This is public data' };
}
@Get('protected')
@RequireAuth() // Require authentication
getProtectedData() {
return { message: 'This is protected data' };
}
@Get('admin-only')
@RequireAuth()
@RequireRoles('admin') // Require specific role
getAdminData() {
return { message: 'Admin only data' };
}
@Get('user-data')
@RequireAuth()
@RequirePermissions('read:user') // Require specific permission
getUserData() {
return { message: 'User data' };
}
@Get('default')
// No decorator - accessible without authentication by default
getDefaultData() {
return { message: 'Default accessible data' };
}
}Guard Priority
@Public()- Always allows access (highest priority)@RequireAuth()- Requires authentication- No decorator - Allows access by default (lowest priority)
This approach provides better scalability by allowing you to:
- Start with open access and gradually secure routes as needed
- Avoid accidentally exposing routes due to missing decorators
- Have fine-grained control over authentication requirements
## API Reference
### Core Exports
#### AuthClient
The core authentication client that handles all communication with the auth service. Provides automatic fallback from Redis to HTTP and intelligent error handling.
```typescript
import { AuthClient } from '@science-and-humans/science-and-humans-auth-sdk';
const authClient = new AuthClient({
authServiceUrl: 'http://localhost:3000',
redis: { host: 'localhost', port: 6379 },
});
// Validate JWT token - Primary authentication method
// Verifies token signature, expiration, and retrieves user data
const validation = await authClient.validateToken({ token: 'jwt-token' });
// Returns: { isValid: boolean, user?: ClerkUser, roles?: string[], permissions?: string[] }
// Validate session - Alternative authentication using session ID
// Useful for session-based authentication flows
const session = await authClient.validateSession({ sessionId: 'session-id' });
// Returns: { isValid: boolean, user?: ClerkUser, roles?: string[], permissions?: string[] }
// Check user authorization - Fine-grained permission checking
// Verifies if user has permission to perform specific actions on resources
const auth = await authClient.authorizeUser({
userId: 'user-123',
resource: 'posts',
action: 'create',
});
// Returns: { isAuthorized: boolean, error?: string }
// Health check - Monitor service connectivity and performance
// Checks Redis connection, HTTP fallback, and overall system health
const health = await authClient.healthCheck();
// Returns: { status: string, services: { redis: string, http: string } }
// Cleanup - Properly close connections and cleanup resources
// Important for graceful application shutdown
await authClient.disconnect();AuthMiddlewareModule
NestJS module for authentication setup.
import { AuthMiddlewareModule } from '@science-and-humans/science-and-humans-auth-sdk';
// Static configuration
AuthMiddlewareModule.forRoot(config);
// Async configuration
AuthMiddlewareModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
authServiceUrl: configService.get('AUTH_SERVICE_URL'),
redis: {
host: configService.get('REDIS_HOST'),
port: configService.get('REDIS_PORT'),
},
}),
});Guards
AuthGuard
Core authentication guard that validates tokens and establishes user context. Automatically extracts tokens from Authorization header, validates them, and populates request.auth with user data.
import { AuthGuard } from '@science-and-humans/science-and-humans-auth-sdk';
@UseGuards(AuthGuard)
@Controller('protected')
export class ProtectedController {}
// All routes in this controller require valid authentication
// Populates @CurrentUser() with authenticated user dataRolesGuard
Role-based access control guard that works in conjunction with AuthGuard. Checks user roles and permissions against route requirements defined by decorators.
import { RolesGuard } from '@science-and-humans/science-and-humans-auth-sdk';
@UseGuards(AuthGuard, RolesGuard) // Must be used together
@Controller('admin')
export class AdminController {}
// Enforces role requirements set by @RequireRoles() decorator
// Automatically denies access if user lacks required rolesDecorators
Authentication Decorators
Route-level decorators that control authentication and authorization behavior. These work with guards to enforce security policies.
import {
RequireAuth,
RequireRoles,
RequirePermissions,
Public
} from '@science-and-humans/science-and-humans-auth-sdk';
@Public()
// Skip authentication entirely - accessible to anonymous users
// Overrides any guards applied at controller level
@RequireAuth()
// Explicitly require authentication (default behavior when AuthGuard is used)
// Redundant but useful for documentation purposes
@RequireRoles('admin', 'moderator')
// Require user to have one of the specified roles
// User must have 'admin' OR 'moderator' role to access
@RequirePermissions('read:posts', 'write:posts')
// Require specific permissions for fine-grained access control
// User must have 'read:posts' AND 'write:posts' permissions
#### Organization Authorization Decorators
Advanced decorators for organization-level access control. These decorators require users to be in an organization context and have specific organization roles or permissions.
```typescript
import {
RequireOrgRole,
RequireOrgPermission,
} from '@science-and-humans/science-and-humans-auth-sdk';
@RequireOrgRole('org:admin', 'org:moderator')
// Require user to have one of the specified organization roles
// User must be in organization context AND have 'org:admin' OR 'org:moderator' role
@RequireOrgPermission('org:manage_members', 'org:view_analytics')
// Require specific organization permissions for fine-grained access control
// User must be in organization context AND have 'org:manage_members' AND 'org:view_analytics' permissionsParameter Decorators
Extractors that inject authentication data into route handler parameters. These provide clean access to user context.
import {
CurrentUser,
UserRoles,
AuthInfo
} from '@science-and-humans/science-and-humans-auth-sdk';
getUser(@CurrentUser() user: ClerkUser)
// Inject authenticated user object with full profile data
// Returns null if user is not authenticated
getRoles(@UserRoles() roles: string[])
// Inject array of user's roles
// Returns empty array if no roles or not authenticated
getAuthContext(@AuthInfo() auth: AuthContext)
// Inject complete authentication context
// Includes user, roles, permissions, token info, and auth status
getUserRoleInfo(@GetUserRoleInfo() roleInfo: UserRoleInfo)
// Inject detailed role information including organization context
// Returns role context (user/organization), org role, permissions, and organization IDMiddleware
AuthMiddleware
Core NestJS middleware that processes every request to extract and validate authentication tokens. Automatically populates request.auth with authentication context.
import { AuthMiddleware } from '@science-and-humans/science-and-humans-auth-sdk';
// Automatically registered when using AuthMiddlewareModule
// Runs on every request to:
// 1. Extract JWT tokens from Authorization header
// 2. Validate tokens using AuthClient
// 3. Populate request.auth with user context
// 4. Handle authentication errors gracefullyExpress Support
Factory functions for integrating with Express applications. Provides the same authentication capabilities outside of NestJS.
import {
createAuthMiddleware,
requireAuth,
} from '@science-and-humans/science-and-humans-auth-sdk';
const app = express();
const authClient = new AuthClient(config);
// Apply auth middleware - Processes all requests
app.use(createAuthMiddleware(authClient));
// Extracts tokens and populates req.auth for all routes
// Protect specific routes - Declarative authentication
app.get('/protected', requireAuth(), (req, res) => {
res.json({ user: req.auth?.user });
});
// Ensures user is authenticated, throws 401 if not
// Role-based protection - Fine-grained authorization
app.get('/admin', requireAuth({ roles: ['admin'] }), (req, res) => {
res.json({ message: 'Admin only' });
});
// Requires both authentication AND admin roleException Filters
AuthExceptionFilter
Specialized exception filter that transforms authentication errors into proper HTTP responses. Provides consistent error formatting and logging.
import { AuthExceptionFilter } from '@science-and-humans/science-and-humans-auth-sdk';
@UseFilters(AuthExceptionFilter)
@Controller()
export class MyController {}
// Catches authentication-related exceptions and formats them properly
// Converts internal auth errors to appropriate HTTP status codes (401, 403)
// Provides detailed error messages while protecting sensitive informationGlobalAuthAwareExceptionFilter
Enhanced global exception filter that understands authentication context. Provides better error responses based on user's auth status.
import { GlobalAuthAwareExceptionFilter } from '@science-and-humans/science-and-humans-auth-sdk';
// In main.ts
app.useGlobalFilters(new GlobalAuthAwareExceptionFilter());
// Handles all exceptions with awareness of user authentication state
// Provides different error responses for authenticated vs anonymous users
// Logs security-relevant exceptions for monitoring and debuggingHealth Monitoring
RedisHealthService
Dedicated service for monitoring Redis connection health and performance. Provides real-time status checks and connection management.
import { RedisHealthService } from '@science-and-humans/science-and-humans-auth-sdk';
const healthService = new RedisHealthService({
host: 'localhost',
port: 6379,
});
// Quick health check - Returns boolean status
const isHealthy = await healthService.isHealthy();
// true if Redis is reachable and responsive
// Detailed health status - Returns comprehensive health information
const status = await healthService.getHealthStatus();
// Returns: { status: 'healthy'|'degraded'|'unhealthy', lastCheck: Date, responseTime: number }
// Automatically handles:
// - Connection pooling and cleanup
// - Timeout management
// - Retry logic for transient failures
// - Performance monitoringOrganization Authorization
The SDK provides advanced organization-level authorization capabilities that extend beyond basic user authentication. This feature allows you to manage complex multi-tenant applications with organization-specific roles and permissions.
Organization Context
Organization authorization operates in a distinct context from user-level authorization. When a user is part of an organization, they can have:
- Organization Roles: Roles specific to their position within the organization (e.g.,
org:admin,org:member,org:moderator) - Organization Permissions: Fine-grained permissions for organization-specific actions (e.g.,
org:manage_members,org:view_analytics,org:edit_settings)
Organization Authorization Flow
import { Controller, Get, UseGuards } from '@nestjs/common';
import {
AuthGuard,
RolesGuard,
RequireOrgRole,
RequireOrgPermission,
CurrentUser,
GetUserRoleInfo,
UserRoleInfo,
} from '@science-and-humans/science-and-humans-auth-sdk';
@Controller('organization')
@UseGuards(AuthGuard, RolesGuard)
export class OrganizationController {
@Get('dashboard')
@RequireOrgRole('org:admin', 'org:moderator')
getOrgDashboard(
@CurrentUser() user: ClerkUser,
@GetUserRoleInfo() roleInfo: UserRoleInfo
) {
return {
message: 'Organization dashboard',
user: user.id,
organizationId: roleInfo.organizationId,
orgRole: roleInfo.orgRole,
context: roleInfo.context,
};
}
@Get('members')
@RequireOrgPermission('org:manage_members')
getOrgMembers(@GetUserRoleInfo() roleInfo: UserRoleInfo) {
return {
message: 'Organization members list',
organizationId: roleInfo.organizationId,
permissions: roleInfo.permissions,
};
}
@Get('analytics')
@RequireOrgPermission('org:view_analytics', 'org:admin')
getOrgAnalytics() {
return { message: 'Organization analytics' };
}
}Organization Authorization with AuthClient
The AuthClient supports organization context in all authorization operations:
import { AuthClient } from '@science-and-humans/science-and-humans-auth-sdk';
const authClient = new AuthClient({
authServiceUrl: 'http://localhost:3000',
organizationId: 'org_30yWUKhkqRl4aexwv3aJelcoRbT', // Default org context
});
// Validate token with organization context
const validation = await authClient.validateToken({
token: 'jwt-token',
organizationId: 'org_30yWUKhkqRl4aexwv3aJelcoRbT',
context: 'organization',
});
// Authorize user for organization-specific actions
const auth = await authClient.authorizeUser({
userId: 'user_123',
resource: 'organization_members',
action: 'manage',
organizationId: 'org_30yWUKhkqRl4aexwv3aJelcoRbT',
context: 'organization',
});Organization Types
import {
ClerkOrganization,
OrganizationRole,
OrganizationPermission,
UserRoleInfo,
} from '@science-and-humans/science-and-humans-auth-sdk';
interface UserRoleInfo {
context: 'user' | 'organization';
organizationId?: string;
orgRole?: string;
permissions?: string[];
}
interface ClerkOrganization {
id: string;
name: string;
slug?: string;
imageUrl: string;
hasImage: boolean;
membersCount?: number;
maxAllowedMemberships: number;
adminDeleteEnabled: boolean;
publicMetadata: Record<string, unknown>;
privateMetadata?: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
interface OrganizationRole {
id: string;
key: string;
name: string;
description?: string;
permissions: OrganizationPermission[];
createdAt: Date;
updatedAt: Date;
}
interface OrganizationPermission {
id: string;
key: string;
name: string;
description?: string;
type: 'system' | 'custom';
createdAt: Date;
updatedAt: Date;
}Best Practices for Organization Authorization
- Context Awareness: Always check if the user is in the correct context (user vs organization)
- Role Hierarchy: Design organization roles with clear hierarchies (admin > moderator > member)
- Permission Granularity: Use specific permissions for fine-grained access control
- Default Organization: Set a default organization ID in your configuration for consistent behavior
- Error Handling: Handle organization context errors gracefully with appropriate user feedback
Type Definitions
Core Types
import {
ClerkUser,
ClerkSession,
AuthConfig,
AuthValidationRequest,
AuthValidationResponse,
User,
Role,
Permission,
ClerkOrganization,
OrganizationRole,
OrganizationPermission,
UserRoleInfo,
} from '@science-and-humans/science-and-humans-auth-sdk';Configuration Types
interface AuthConfig {
authServiceUrl: string;
serviceKey?: string;
serviceName?: string;
organizationId?: string; // Default organization context for all requests
requestTimeout?: number;
redis: {
host: string;
port: number;
password?: string;
db?: number;
};
}User Types
interface User {
id: string;
email: string;
firstName?: string;
lastName?: string;
role: string;
isVerified: boolean;
createdAt: Date;
updatedAt: Date;
metadata?: Record<string, any>;
}
interface Role {
id: string;
name: string;
permissions: Permission[];
description?: string;
}
interface Permission {
id: string;
name: string;
resource: string;
action: string;
description?: string;
}Configuration Examples
Environment-based Configuration
AuthMiddlewareModule.forRoot({
authServiceUrl: process.env.AUTH_SERVICE_URL || 'http://localhost:3000',
serviceKey: process.env.AUTH_SERVICE_KEY,
serviceName: process.env.SERVICE_NAME || 'my-service',
organizationId: process.env.ORGANIZATION_ID, // Default organization context
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: parseInt(process.env.REDIS_DB || '0'),
},
requestTimeout: parseInt(process.env.AUTH_TIMEOUT || '5000'),
});Async Configuration with ConfigService
AuthMiddlewareModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
authServiceUrl: configService.get('auth.serviceUrl'),
serviceKey: configService.get('auth.serviceKey'),
serviceName: configService.get('app.name'),
redis: {
host: configService.get('redis.host'),
port: configService.get('redis.port'),
password: configService.get('redis.password'),
db: configService.get('redis.db'),
},
}),
});Error Handling
The middleware distinguishes between different types of errors:
- Authentication Errors: Invalid/expired tokens → Returns 401 immediately
- Infrastructure Errors: Redis/network issues → Triggers HTTP fallback
- Authorization Errors: Insufficient permissions → Returns 403
try {
// Protected operation
} catch (error) {
if (error instanceof UnauthorizedException) {
// Handle authentication failure
} else if (error instanceof ForbiddenException) {
// Handle authorization failure
}
}Health Monitoring
// Check overall health
const health = await authClient.healthCheck();
console.log(health);
// { status: 'healthy', services: { redis: 'up', http: 'up' } }
// Monitor Redis specifically
const redisHealth = new RedisHealthService(redisConfig);
const isHealthy = await redisHealth.isHealthy();Best Practices
- Always use environment variables for sensitive configuration
- Combine AuthGuard with RolesGuard for role-based access control
- Use @Public() decorator to explicitly mark public endpoints
- Monitor health status in production environments
- Handle errors gracefully with proper exception filters
License
MIT License
