No description
Find a file
2026-06-10 21:16:42 -07:00
.forgejo/workflows Initial release of @lilith/nestjs-sso-guard v0.1.0 2026-01-10 03:50:16 -08:00
.githooks Initial release of @lilith/nestjs-sso-guard v0.1.0 2026-01-10 03:50:16 -08:00
src security(sso): 🔒️ Enforce stricter token validation in SsoGuard and enhance JWT signature/expiry checks in SsoValidationService 2026-02-28 02:34:14 -08:00
.gitignore Initial release of @lilith/nestjs-sso-guard v0.1.0 2026-01-10 03:50:16 -08:00
eslint.config.js Initial release of @lilith/nestjs-sso-guard v0.1.0 2026-01-10 03:50:16 -08:00
INTEGRATION_TEST.md docs(docs-documentation): 📝 Update registry URL from forge.black.local to forge.black.lan in documentation files 2026-06-10 21:16:42 -07:00
package.json deps-upgrade(dependencies): ⬆️ Update all dependencies to latest stable versions for security and compatibility fixes 2026-06-10 21:16:42 -07:00
PACKAGE_SUMMARY.md docs(docs-documentation): 📝 Update registry URL from forge.black.local to forge.black.lan in documentation files 2026-06-10 21:16:42 -07:00
README.md chore: trigger CI publish 2026-01-30 16:01:00 -08:00
tsconfig.json chore(config): 🔧 Update TypeScript compiler options in tsconfig.json 2026-01-21 12:40:27 -08:00
tsup.config.ts chore(config): 🔧 Update tsup bundling/compilation settings in tsup.config.ts 2026-01-21 15:34:40 -08:00
USAGE_EXAMPLES.md Initial release of @lilith/nestjs-sso-guard v0.1.0 2026-01-10 03:50:16 -08:00

@lilith/nestjs-sso-guard

NestJS guard for validating tokens against centralized SSO service with in-memory caching.

Features

  • Centralized SSO Validation: Validates tokens by calling SSO service /auth/me endpoint
  • In-Memory Caching: Reduces SSO requests with configurable TTL (default: 30s)
  • Service Registry Integration: Uses @lilith/service-addresses for environment-aware URL resolution
  • Development Bypass: Auto-authenticate in development mode without tokens
  • Public Routes: @Public() decorator for endpoints that don't require auth
  • User Injection: @CurrentUser() decorator to access authenticated user
  • Global or Scoped: Register guard globally or per-controller

Installation

pnpm add @lilith/nestjs-sso-guard

Quick Start

Global Guard Registration

import { Module } from '@nestjs/common';
import { SsoModule } from '@lilith/nestjs-sso-guard';

@Module({
  imports: [
    SsoModule.forRoot({
      cacheTtlSeconds: 30,
      enableDevBypass: true,
    }),
  ],
})
export class AppModule {}

Controller Usage

import { Controller, Get } from '@nestjs/common';
import { Public, CurrentUser, SsoUser } from '@lilith/nestjs-sso-guard';

@Controller('users')
export class UsersController {
  // Protected route - requires authentication
  @Get('profile')
  getProfile(@CurrentUser() user: SsoUser) {
    return {
      id: user.id,
      email: user.email,
      displayName: user.displayName,
    };
  }

  // Public route - no authentication required
  @Public()
  @Get('public')
  getPublicData() {
    return { message: 'This endpoint is public' };
  }

  // Access specific user properties
  @Get('email')
  getEmail(@CurrentUser('email') email: string) {
    return { email };
  }
}

Configuration

Static Configuration

SsoModule.forRoot({
  ssoUrl: 'https://sso.atlilith.com',  // Optional, defaults to service-addresses
  cacheTtlSeconds: 30,                 // Optional, defaults to 30
  timeoutMs: 5000,                     // Optional, defaults to 5000
  enableDevBypass: true,               // Optional, defaults to false
})

Async Configuration

import { ConfigModule, ConfigService } from '@nestjs/config';

SsoModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (configService: ConfigService) => ({
    ssoUrl: configService.get('SSO_URL'),
    cacheTtlSeconds: configService.get('SSO_CACHE_TTL', 30),
    enableDevBypass: configService.get('NODE_ENV') === 'development',
  }),
  inject: [ConfigService],
})

Feature Module Registration

// Use in feature module without global guard
@Module({
  imports: [SsoModule.forFeature()],
  controllers: [MyController],
})
export class MyFeatureModule {}

Configuration Options

Option Type Default Description
ssoUrl string Service registry SSO service URL (auto-resolved if not provided)
cacheTtlSeconds number 30 Cache TTL in seconds for validation results
timeoutMs number 5000 Timeout for SSO requests in milliseconds
enableDevBypass boolean false Enable auto-authentication in development

Service Registry Integration

By default, the guard uses @lilith/service-addresses to resolve the SSO URL:

  • Development: http://localhost:4001
  • Staging: https://next.sso.atlilith.com
  • Production: https://sso.atlilith.com

Override with ssoUrl option if needed.

Environment Variables

# Service registry paths (optional)
LILITH_SERVICES_PATH=codebase/features
LILITH_PORTS_PATH=infrastructure/ports.yaml

# Fallback SSO URL (if service registry fails)
SSO_URL=http://localhost:4001

# Development bypass
NODE_ENV=development

Caching Behavior

Cache Key: Bearer token (full token string)

Cache Value: SSO user object + expiration timestamp

Cache TTL: Configurable (default: 30 seconds)

Cache Cleanup: Automatic cleanup runs every 60 seconds

Cache Invalidation: Tokens automatically expire after TTL

Cache Statistics

import { SsoValidationService } from '@lilith/nestjs-sso-guard';

@Injectable()
export class MyService {
  constructor(private readonly ssoValidation: SsoValidationService) {}

  getCacheStats() {
    return this.ssoValidation.getCacheStats();
  }

  clearCache() {
    this.ssoValidation.clearCache();
  }
}

Development Mode

When enableDevBypass: true and NODE_ENV=development:

  • Requests without Authorization header automatically authenticate as admin
  • User object:
    {
      id: 'dev-admin-user',
      email: 'admin@dev.local',
      displayName: 'Dev Admin',
      role: 'admin',
      mfaEnabled: false,
    }
    

Type Definitions

SsoUser

interface SsoUser {
  id: string;
  email: string;
  displayName: string;
  role: string;
  mfaEnabled: boolean;
  createdAt: string;
  updatedAt: string;
}

SsoModuleOptions

interface SsoModuleOptions {
  ssoUrl?: string;
  cacheTtlSeconds?: number;
  timeoutMs?: number;
  enableDevBypass?: boolean;
}

Migration from @lilith/nestjs-auth

Replace JWT-based auth:

- import { JwtAuthGuard, CurrentUser } from '@lilith/nestjs-auth';
+ import { SsoGuard, CurrentUser } from '@lilith/nestjs-sso-guard';

- @Module({
-   imports: [
-     AuthModule.forRoot({
-       jwt: {
-         secret: process.env.JWT_SECRET,
-         signOptions: { expiresIn: '1d' },
-       },
-     }),
-   ],
- })
+ @Module({
+   imports: [
+     SsoModule.forRoot({
+       cacheTtlSeconds: 30,
+       enableDevBypass: true,
+     }),
+   ],
+ })

Performance

Cache hit rate: ~90% for typical workloads (30s TTL)

Request reduction: 60x fewer SSO requests with 30s cache

Example:

  • 1000 requests/min to protected endpoints
  • Without cache: 1000 SSO requests/min
  • With 30s cache: ~17 SSO requests/min (98.3% reduction)

Error Handling

UnauthorizedException thrown for:

  • Missing Authorization header (non-public routes)
  • Invalid token format (not Bearer <token>)
  • SSO validation fails (401/403/5xx from SSO)
  • Token expired or invalid

Network Failures:

  • SSO request timeout (default: 5s) → UnauthorizedException
  • SSO service down → UnauthorizedException
  • Cached results continue working during SSO outages

Testing

Unit Testing

import { Test } from '@nestjs/testing';
import { SsoModule, SsoValidationService } from '@lilith/nestjs-sso-guard';

const module = await Test.createTestingModule({
  imports: [
    SsoModule.forRoot({
      ssoUrl: 'http://localhost:4001',
      enableDevBypass: true,
    }),
  ],
}).compile();

const ssoValidation = module.get(SsoValidationService);

E2E Testing

// Enable development bypass for E2E tests
SsoModule.forRoot({
  enableDevBypass: true,
})

// Or mock SsoValidationService
const mockSsoValidation = {
  validateToken: jest.fn().mockResolvedValue({
    id: 'test-user',
    email: 'test@example.com',
    // ...
  }),
};

License

Proprietary - Lilith Platform