Secrets Management

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 7 min

Secrets Management is the secure handling of sensitive data (like database passwords, API keys, and JWT secrets) to ensure they are never hardcoded in source code or accidentally exposed in logs.

Overview

A “Secret” is any string that, if exposed, would allow an attacker to compromise your application.

The most common way applications get hacked is when a developer accidentally commits a .env file containing production database credentials to a public GitHub repository. Within seconds, automated bots scrape the repository and steal the database.

NestJS provides the @nestjs/config package to securely load secrets from the environment, ensuring they are kept completely separate from the codebase.

Key Concepts

  • Environment Variables (.env): Files that store configuration locally. They must never be committed to version control.
  • Secret Managers: Cloud services (like AWS Secrets Manager, HashiCorp Vault, or Google Cloud Secret Manager) that securely store and inject secrets into your application at runtime.
  • ConfigService: The NestJS service used to retrieve configuration values safely, with built-in fallback and typing support.

Code Examples

1. Basic Setup (@nestjs/config)

First, install the configuration package.

npm install @nestjs/config

Create a .env file in the root of your project:

# .env (NEVER COMMIT THIS FILE)
DATABASE_PASSWORD=super_secret_p@ssw0rd!
JWT_SECRET=x9f8h23jf...

Load the module globally in AppModule:

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    // This automatically loads the .env file and parses it
    ConfigModule.forRoot({
      isGlobal: true, 
      // Optional: Ignore the .env file in production (relying on OS env vars instead)
      // ignoreEnvFile: process.env.NODE_ENV === 'production', 
    }),
  ],
})
export class AppModule {}

2. Safely Accessing Secrets

Never use process.env.JWT_SECRET directly in your code. It is untyped and difficult to mock during testing. Always inject the ConfigService.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
  constructor(private configService: ConfigService) {}

  generateToken() {
    // If the secret is missing, this will fail gracefully or you can enforce it
    const secret = this.configService.get<string>('JWT_SECRET');
    if (!secret) {
      throw new Error('FATAL: JWT_SECRET is not defined');
    }
    
    // ... use the secret
  }
}

3. Validating Secrets on Startup (Fail Fast)

If you deploy to production but forget to set the DATABASE_PASSWORD environment variable, your app will start up successfully, but crash the moment a user tries to log in.

You should use Joi or class-validator to validate that all required secrets exist before NestJS is allowed to start.

npm install joi
import * as Joi from 'joi';

@Module({
  imports: [
    ConfigModule.forRoot({
      // If any of these are missing, the app crashes immediately on startup!
      validationSchema: Joi.object({
        NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
        PORT: Joi.number().default(3000),
        DATABASE_PASSWORD: Joi.string().required(),
        JWT_SECRET: Joi.string().required(),
      }),
    }),
  ],
})
export class AppModule {}

Best Practices

  • Rotate Secrets Regularly: If an employee leaves the company, you must change all API keys and database passwords they had access to. Using an automated Secret Manager (like AWS KMS) can rotate these keys for you automatically every 30 days without changing any code.
  • Scrub Logs: Ensure your application logger (e.g., Pino or Winston) is configured to automatically redact or mask fields named password, token, or secret so they never accidentally end up in Datadog or CloudWatch.