Skip to content

Quick Start Guide

Azizul Hakim edited this page Nov 4, 2024 · 7 revisions

After installing nestjs-xsecurity, follow these steps to implement security in your NestJS application.

Module Configuration

You can configure the XSecurity module using one of two approaches:

⭐ Recommended: Async Configuration

The async configuration approach is recommended as it provides better flexibility and integration with NestJS's configuration system:

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { XSecurityModule } from 'nestjs-xsecurity';

@Module({
  imports: [
    ConfigModule.forRoot(),
    XSecurityModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        enabled: config.get('XSECURITY_ENABLED', true),
        secret: config.get('XSECURITY_SECRET'),
        rateLimit: {
          enabled: config.get('XSECURITY_RATE_LIMIT_ENABLED', true),
          maxAttempts: config.get('XSECURITY_MAX_ATTEMPTS', 5),
          decayMinutes: config.get('XSECURITY_DECAY_MINUTES', 1),
          storeLimit: config.get('XSECURITY_RATE_LIMIT_STORE_LIMIT', 10000)
        },
        exclude: ['/health', '/metrics', '/api/docs/*'],
      }),
    }),
  ],
})
export class AppModule {}

Alternative: Static Configuration

For simpler applications, you can use static configuration:

import { Module } from '@nestjs/common';
import { XSecurityModule } from 'nestjs-xsecurity';

@Module({
  imports: [
    XSecurityModule.register({
      enabled: true,
      secret: process.env.XSECURITY_SECRET,
      rateLimit: {
        enabled: true
        maxAttempts: 5,
        decayMinutes: 1,
        storeLimit: 100
      },
      exclude: ['/health'],
    }),
  ],
})
export class AppModule {}

Separating Configuration Logic

For better organization, especially in larger applications, you can separate the configuration logic into a dedicated file:

// xsecurity.config.ts
import { ConfigService } from '@nestjs/config';
import { XSecurityConfig } from 'nestjs-xsecurity';

export class XSecurityConfig {
  constructor(private readonly configService: ConfigService) {}

  configureOptions(): XSecurityConfig {
    return {
      enabled: this.configService.get<boolean>('XSECURITY_ENABLED') ?? true,
      secret: this.configService.get<string>('XSECURITY_SECRET'),
      token: {
        headerName: 'X-SECURITY-TOKEN',
      },
      rateLimit: {
        enabled: config.get('XSECURITY_RATE_LIMIT_ENABLED', true),
        maxAttempts: 3,
        decayMinutes: 1,
        storeLimit: config.get('XSECURITY_RATE_LIMIT_STORE_LIMIT', 10000)
      },
      exclude: ['/health'],
    };
  }
}

export default (configService: ConfigService) =>
  new XSecurityConfig(configService).configureOptions();

Then use it in your app module:

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { XSecurityModule } from 'nestjs-xsecurity';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import xsecurityConfig from './xsecurity.config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
      cache: true,
    }),
    XSecurityModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) =>
        xsecurityConfig(configService),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Token Generation

Here are implementations for generating security tokens in various languages:

Node.js / TypeScript

import crypto from 'crypto';

function generateXsecurityToken(secretKey: string, expirySeconds = 300): string {
  const expiryTimestamp = Math.floor(Date.now() / 1000) + expirySeconds;
  const randomBytes = crypto.randomBytes(16).toString('hex'); // Add randomness
  const payload = {
    expiry: expiryTimestamp,
    nonce: randomBytes,
    iat: Date.now()
  };

  const token = Buffer.from(JSON.stringify(payload)).toString('base64');
  const signature = crypto
    .createHmac('sha256', secretKey)
    .update(token)
    .digest('hex');

  return `${token}.${signature}`;
}

// Usage
const token = generateXSecurityToken('your-secret-key');

Python

import time
import json
import hmac
import base64
import secrets
import hashlib
from typing import Optional

def generate_xsecurity_token(secret_key: str, expiry_seconds: int = 300) -> str:
    """
    Generate a secure token with expiry and nonce.

    Args:
        secret_key (str): Secret key for signing
        expiry_seconds (int): Token validity duration in seconds

    Returns:
        str: Generated security token
    """
    expiry_timestamp = int(time.time()) + expiry_seconds
    random_bytes = secrets.token_hex(16)  # 16 bytes = 32 hex chars

    payload = {
        "expiry": expiry_timestamp,
        "nonce": random_bytes,
        "iat": int(time.time() * 1000)  # milliseconds
    }

    # Convert payload to base64
    token = base64.b64encode(
        json.dumps(payload).encode('utf-8')
    ).decode('utf-8')

    # Generate signature
    signature = hmac.new(
        secret_key.encode('utf-8'),
        token.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return f"{token}.{signature}"

Flutter / Dart

import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';

class XSecurityToken {
  static String generate(String secretKey, {int expirySeconds = 300}) {
    final expiryTimestamp = (DateTime.now().millisecondsSinceEpoch ~/ 1000) + expirySeconds;

    // Generate random bytes for nonce
    final random = Random.secure();
    final randomBytes = List<int>.generate(16, (i) => random.nextInt(256));
    final nonce = randomBytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();

    final payload = {
      'expiry': expiryTimestamp,
      'nonce': nonce,
      'iat': DateTime.now().millisecondsSinceEpoch
    };

    // Convert payload to base64
    final token = base64Encode(utf8.encode(jsonEncode(payload)));

    // Generate signature
    final hmacSha256 = Hmac(sha256, utf8.encode(secretKey));
    final signature = hmacSha256.convert(utf8.encode(token)).toString();

    return '$token.$signature';
  }
}

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;

public class XSecurityToken {
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
    public static String generate(String secretKey, int expirySeconds) 
            throws NoSuchAlgorithmException, InvalidKeyException {
        long expiryTimestamp = Instant.now().getEpochSecond() + expirySeconds;
        
        // Generate random bytes for nonce
        byte[] randomBytes = new byte[16];
        SECURE_RANDOM.nextBytes(randomBytes);
        String nonce = bytesToHex(randomBytes);
        
        // Create payload
        Map<String, Object> payload = new HashMap<>();
        payload.put("expiry", expiryTimestamp);
        payload.put("nonce", nonce);
        payload.put("iat", System.currentTimeMillis());
        
        try {
            // Convert payload to base64
            String jsonPayload = OBJECT_MAPPER.writeValueAsString(payload);
            String token = Base64.getEncoder()
                .encodeToString(jsonPayload.getBytes(StandardCharsets.UTF_8));
            
            // Generate signature
            Mac hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(
                secretKey.getBytes(StandardCharsets.UTF_8), 
                "HmacSHA256"
            );
            hmac.init(secretKeySpec);
            
            byte[] signatureBytes = hmac.doFinal(token.getBytes(StandardCharsets.UTF_8));
            String signature = bytesToHex(signatureBytes);
            
            return token + "." + signature;
            
        } catch (Exception e) {
            throw new RuntimeException("Failed to generate security token", e);
        }
    }
    
    private static String bytesToHex(byte[] bytes) {
        StringBuilder result = new StringBuilder();
        for (byte b : bytes) {
            result.append(String.format("%02x", b));
        }
        return result.toString();
    }
}

Next Steps