Securing Secrets in Nest.js: A Guide to Using AWS Secret Manager for App Credentials

Nikhil Jain
Simform Engineering
5 min readApr 28, 2023
AWS Secrets Manager + NestJS

Managing secrets using env variables from .env files in node frameworks has been the most popular method among developers. However, env variables have some drawbacks when it comes to managing secrets.

Firstly, they can be hard to manage at scale. As the number of secrets grows, managing them using environment variables becomes challenging.

Additionally, env variables can be difficult to rotate, especially when managing credentials for multiple services. Finally, env variables can be compromised when someone gains access.

To overcome all these drawbacks, AWS comes to the rescue with its Secrets Manager service. It is a managed service that provides a secure and scalable way of managing secrets.

Secrets can be encrypted and decrypted using industry-standard encryption algorithms, and access to secrets can be restricted using AWS Identity and Access Management (IAM) policies.

NestJS has built-in support for retrieving secrets from AWS Secrets Manager using the @nestjs/config module. This means that you can easily configure your NestJS application to read secrets from AWS Secrets Manager, and the secrets will be automatically loaded into your application

While integrating, a few challenges can be observed, such as loading asynchronously in the config module and accessing the secrets in the main.ts file.

But you can rest assured as in this blog; I will guide you through the steps to integrate AWS Secret Manager in your NestJS application.

Step 1: Set up AWS Secret Manager

First, you need to create secrets in AWS Secret Manager. You can do this by following these steps:

  1. Log in to the AWS Management Console and navigate to AWS Secret Manager.
  2. Click on the “Store a new secret” button.
  3. Choose the type of secret you want to store (for example, a database password).
  4. Enter the personal value and any additional configuration options.
  5. Click on the “Next” button and give your secret a name.
  6. Click on the “Next” button again and review your settings.
  7. Click on the “Store” button to save your secret.

Once you have created your secret, you can use it in your NestJS application. The code example used in this blog expects the following secrets from AWS Secret Manager:

Secrets stored in AWS Secret Manager

Step 2: Create a helper function that fetches secrets from the Secrets Manager

  1. Install the aws-sdk for the secrets manager client:
npm install --save @aws-sdk/client-secrets-manager

2. Install the config module from NestJS:

As mentioned above, we’ll be using the config module of NestJS to load secrets in the application, so we will install @nestjs/config package as well.

npm install --save @nestjs/config

3. Create a helper function that fetches secrets from Secret Manager:

We’ll create a helper function in a file named fetch-screts.ts within the shared directory in the root directory.

import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
import { ConfigService } from '@nestjs/config';

export const fetchSecrets = async (secretName: string) => {
const configService = new ConfigService();

const client = new SecretsManagerClient({
region: configService.get('AWS_REGION'),
});
try {
const response = await client.send(
new GetSecretValueCommand({
SecretId: secretName,
}),
);
return JSON.parse(response.SecretString);
} catch (error) {
throw error;
}
};

Note: Here, ‘AWS_REGION’ is an env variable stored in a .env file.

Step 3: Create a custom configuration file

Now we will create a custom configuration in a file named secret-manager.config.ts within the config directory in the root directory that fetches secrets from Secrets Manager using the helper function created above and returns secrets in JSON format.

import { ConfigService } from '@nestjs/config';
import { fetchSecrets } from '../shared/fetch-secrets';

export default async () => {
const configService = new ConfigService();
const secretName = configService.get('AWS_SECRET_NAME');
const secrets = await fetchSecrets(secretName);
return {
port: +secrets.PORT,
database: {
host: secrets.DB_HOST,
port: +secrets.DB_PORT,
name: secrets.DB_NAME,
password: secrets.DB_PASSWORD,
type: secrets.DB_TYPE,
user: secrets.DB_USER,
},
};
};

Step 4: Create a custom config module and load the custom configuration in it

We’ll create a CustomConfigModule in a file named custom-config.module.ts within the config directory in the root directory.

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import secretManagerConfig from './secret-manager.config';

@Module({
imports: [
ConfigModule.forRoot({
load: [secretManagerConfig],
isGlobal: true,
}),
],
})
export class CustomConfigModule {}

Note: Remember to import this module in app.module.ts

Step 5: The config service loads secrets in the TypeORM data source configuration.

We’ll create a DatabaseModule in a file named database.module.ts within the database directory in the root directory.

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DataSource } from 'typeorm';

export const databaseProvider = [
{
provide: 'DATA_SOURCE',
useFactory: async (configService: ConfigService) => {
const secrets = configService.get('database');
const connection = new DataSource({
type: secrets.type,
host: secrets.host,
port: secrets.port,
username: secrets.user,
password: secrets.password,
database: secrets.name,
entities: [],
synchronize: false,
logging: true,
});
return await connection.initialize();
},
inject: [ConfigService],
},
];

@Module({
providers: databaseProvider,
exports: databaseProvider,
})
export class DatabaseModule {}

Note: Remember to import this module in app.module.ts

Bonus:

You must be wondering how to use config service in main.ts, and the answer to this is quite a si: we can create an instance of ConfigService and use it to fetch the secrets using the getter class. But while doing this, you may notice that the value of secrets is undefined.

This can happen because when the secrets are accessed in main.ts, the dependent module might not have been initialized. To overcome this challenge, we can get the instance of config service as follows:

  const configService = app.select(CustomConfigModule).get(ConfigService);
const port = configService.get('port');
app.listen(port, () => {
console.log('Application started on port: ', port);
});

Conclusion:

Integrating AWS Secret Manager in NestJS can help you securely manage your application’s sensitive data.

  • By following the steps outlined in this blog, you can create a custom config module that loads secrets from AWS Secret Manager.
  • You can then inject ConfigService it into your NestJS application and use it to retrieve secrets when needed.
  • This approach can help you avoid hardcoding sensitive information in your code, making your application more secure and easier to maintain.

PS: To clone the whole code base, visit here.

Follow Simform Engineering for all the latest trends in development ecosystem!

--

--