Back to Blog

Advanced Security Patterns: Building Secure Applications in 2025

Cap
11 min read
securitypatternsauthenticationauthorizationencryption

Advanced Security Patterns: Building Secure Applications in 2025

Introduction

Security continues to be a critical aspect of modern applications in 2025, with new patterns and practices emerging to handle evolving threats. This comprehensive guide explores advanced security patterns and their practical applications.

Authentication Patterns

1. Multi-Factor Authentication

// auth.service.ts
interface AuthConfig {
  jwtSecret: string;
  totpIssuer: string;
  smsProvider: SMSProvider;
  emailProvider: EmailProvider;
}

class AuthService {
  constructor(
    private readonly config: AuthConfig,
    private readonly userRepository: UserRepository
  ) {}

  async login(email: string, password: string): Promise<LoginResponse> {
    const user = await this.userRepository.findByEmail(email);
    if (!user) {
      throw new AuthError('Invalid credentials');
    }

    const isValidPassword = await bcrypt.compare(password, user.passwordHash);
    if (!isValidPassword) {
      throw new AuthError('Invalid credentials');
    }

    // Generate MFA options
    const mfaOptions = await this.generateMFAOptions(user);

    return {
      userId: user.id,
      mfaRequired: true,
      mfaOptions
    };
  }

  private async generateMFAOptions(user: User): Promise<MFAOptions> {
    const options: MFAOptions = {
      totp: await this.generateTOTPSecret(user),
      sms: user.phone ? true : false,
      email: true
    };

    return options;
  }

  private async generateTOTPSecret(user: User): Promise<string> {
    const secret = speakeasy.generateSecret({
      length: 20,
      name: `${this.config.totpIssuer}:${user.email}`
    });

    await this.userRepository.update(user.id, {
      totpSecret: secret.base32
    });

    return secret.base32;
  }

  async verifyMFA(
    userId: string,
    mfaType: 'totp' | 'sms' | 'email',
    code: string
  ): Promise<AuthToken> {
    const user = await this.userRepository.findById(userId);
    if (!user) {
      throw new AuthError('User not found');
    }

    let isValid = false;

    switch (mfaType) {
      case 'totp':
        isValid = await this.verifyTOTP(user, code);
        break;
      case 'sms':
        isValid = await this.verifySMSCode(user, code);
        break;
      case 'email':
        isValid = await this.verifyEmailCode(user, code);
        break;
    }

    if (!isValid) {
      throw new AuthError('Invalid MFA code');
    }

    return this.generateAuthToken(user);
  }

  private async verifyTOTP(user: User, code: string): Promise<boolean> {
    return speakeasy.totp.verify({
      secret: user.totpSecret,
      encoding: 'base32',
      token: code,
      window: 1
    });
  }

  private async verifySMSCode(user: User, code: string): Promise<boolean> {
    const storedCode = await this.userRepository.getSMSCode(user.id);
    return storedCode === code;
  }

  private async verifyEmailCode(user: User, code: string): Promise<boolean> {
    const storedCode = await this.userRepository.getEmailCode(user.id);
    return storedCode === code;
  }

  private generateAuthToken(user: User): AuthToken {
    return jwt.sign(
      {
        sub: user.id,
        email: user.email,
        roles: user.roles
      },
      this.config.jwtSecret,
      {
        expiresIn: '1h',
        algorithm: 'RS256'
      }
    );
  }
}

2. Passwordless Authentication

// passwordless.service.ts
interface PasswordlessConfig {
  magicLinkExpiry: number;
  emailProvider: EmailProvider;
  jwtSecret: string;
}

class PasswordlessService {
  constructor(
    private readonly config: PasswordlessConfig,
    private readonly userRepository: UserRepository
  ) {}

  async initiateLogin(email: string): Promise<void> {
    const user = await this.userRepository.findByEmail(email);
    if (!user) {
      // Don't reveal if user exists
      return;
    }

    const token = this.generateMagicLinkToken(user);
    await this.sendMagicLink(email, token);
  }

  private generateMagicLinkToken(user: User): string {
    return jwt.sign(
      {
        sub: user.id,
        email: user.email,
        type: 'magic_link'
      },
      this.config.jwtSecret,
      {
        expiresIn: this.config.magicLinkExpiry,
        algorithm: 'RS256'
      }
    );
  }

  private async sendMagicLink(email: string, token: string): Promise<void> {
    const magicLink = `${process.env.APP_URL}/auth/magic-link?token=${token}`;
    await this.config.emailProvider.sendEmail({
      to: email,
      subject: 'Your Login Link',
      text: `Click here to log in: ${magicLink}`,
      html: `
        <p>Click the link below to log in:</p>
        <a href="${magicLink}">Log In</a>
        <p>This link will expire in ${this.config.magicLinkExpiry / 60} minutes.</p>
      `
    });
  }

  async verifyMagicLink(token: string): Promise<AuthToken> {
    try {
      const decoded = jwt.verify(token, this.config.jwtSecret, {
        algorithms: ['RS256']
      }) as MagicLinkPayload;

      if (decoded.type !== 'magic_link') {
        throw new AuthError('Invalid token type');
      }

      const user = await this.userRepository.findById(decoded.sub);
      if (!user) {
        throw new AuthError('User not found');
      }

      return this.generateAuthToken(user);
    } catch (error) {
      throw new AuthError('Invalid or expired magic link');
    }
  }

  private generateAuthToken(user: User): AuthToken {
    return jwt.sign(
      {
        sub: user.id,
        email: user.email,
        roles: user.roles
      },
      this.config.jwtSecret,
      {
        expiresIn: '1h',
        algorithm: 'RS256'
      }
    );
  }
}

Authorization Patterns

1. Role-Based Access Control

// rbac.service.ts
interface Role {
  name: string;
  permissions: Permission[];
}

interface Permission {
  resource: string;
  action: 'create' | 'read' | 'update' | 'delete';
}

class RBACService {
  constructor(
    private readonly roleRepository: RoleRepository,
    private readonly userRepository: UserRepository
  ) {}

  async assignRole(userId: string, roleName: string): Promise<void> {
    const role = await this.roleRepository.findByName(roleName);
    if (!role) {
      throw new Error('Role not found');
    }

    await this.userRepository.addRole(userId, role);
  }

  async hasPermission(
    userId: string,
    resource: string,
    action: Permission['action']
  ): Promise<boolean> {
    const user = await this.userRepository.findById(userId);
    if (!user) {
      return false;
    }

    const roles = await this.roleRepository.findByNames(user.roles);
    const permissions = roles.flatMap(role => role.permissions);

    return permissions.some(
      permission =>
        permission.resource === resource && permission.action === action
    );
  }

  async checkPermission(
    userId: string,
    resource: string,
    action: Permission['action']
  ): Promise<void> {
    const hasPermission = await this.hasPermission(userId, resource, action);
    if (!hasPermission) {
      throw new AuthorizationError(
        `User does not have permission to ${action} ${resource}`
      );
    }
  }
}

// Usage in controller
@Controller('projects')
class ProjectController {
  constructor(
    private readonly projectService: ProjectService,
    private readonly rbacService: RBACService
  ) {}

  @Post()
  async createProject(
    @Request() req,
    @Body() createProjectDto: CreateProjectDto
  ): Promise<Project> {
    await this.rbacService.checkPermission(
      req.user.id,
      'projects',
      'create'
    );

    return this.projectService.create(createProjectDto);
  }

  @Get(':id')
  async getProject(
    @Request() req,
    @Param('id') id: string
  ): Promise<Project> {
    await this.rbacService.checkPermission(
      req.user.id,
      'projects',
      'read'
    );

    return this.projectService.findById(id);
  }
}

2. Attribute-Based Access Control

// abac.service.ts
interface Policy {
  effect: 'allow' | 'deny';
  action: string;
  resource: string;
  conditions: Condition[];
}

interface Condition {
  attribute: string;
  operator: 'equals' | 'contains' | 'startsWith' | 'endsWith' | 'matches';
  value: any;
}

class ABACService {
  constructor(private readonly policyRepository: PolicyRepository) {}

  async evaluatePolicy(
    user: User,
    action: string,
    resource: string,
    context: Record<string, any>
  ): Promise<boolean> {
    const policies = await this.policyRepository.findByResource(resource);
    const attributes = this.getAttributes(user, context);

    for (const policy of policies) {
      if (policy.action !== action) {
        continue;
      }

      const isAllowed = this.evaluateConditions(policy.conditions, attributes);
      if (isAllowed) {
        return policy.effect === 'allow';
      }
    }

    return false;
  }

  private getAttributes(
    user: User,
    context: Record<string, any>
  ): Record<string, any> {
    return {
      user: {
        id: user.id,
        email: user.email,
        roles: user.roles,
        department: user.department,
        location: user.location
      },
      resource: context.resource,
      action: context.action,
      time: new Date(),
      ip: context.ip,
      device: context.device
    };
  }

  private evaluateConditions(
    conditions: Condition[],
    attributes: Record<string, any>
  ): boolean {
    return conditions.every(condition => {
      const value = this.getAttributeValue(attributes, condition.attribute);
      return this.evaluateCondition(condition, value);
    });
  }

  private getAttributeValue(
    attributes: Record<string, any>,
    path: string
  ): any {
    return path.split('.').reduce((obj, key) => obj?.[key], attributes);
  }

  private evaluateCondition(condition: Condition, value: any): boolean {
    switch (condition.operator) {
      case 'equals':
        return value === condition.value;
      case 'contains':
        return value.includes(condition.value);
      case 'startsWith':
        return value.startsWith(condition.value);
      case 'endsWith':
        return value.endsWith(condition.value);
      case 'matches':
        return new RegExp(condition.value).test(value);
      default:
        return false;
    }
  }
}

// Usage in controller
@Controller('documents')
class DocumentController {
  constructor(
    private readonly documentService: DocumentService,
    private readonly abacService: ABACService
  ) {}

  @Get(':id')
  async getDocument(
    @Request() req,
    @Param('id') id: string
  ): Promise<Document> {
    const isAllowed = await this.abacService.evaluatePolicy(
      req.user,
      'read',
      'documents',
      {
        resource: { id, type: 'document' },
        action: 'read',
        ip: req.ip,
        device: req.headers['user-agent']
      }
    );

    if (!isAllowed) {
      throw new AuthorizationError('Access denied');
    }

    return this.documentService.findById(id);
  }
}

Encryption Patterns

1. Data Encryption

// encryption.service.ts
interface EncryptionConfig {
  algorithm: string;
  keySize: number;
  ivSize: number;
  saltSize: number;
  iterations: number;
}

class EncryptionService {
  constructor(private readonly config: EncryptionConfig) {}

  async encrypt(data: string, key: string): Promise<EncryptedData> {
    const salt = crypto.randomBytes(this.config.saltSize);
    const iv = crypto.randomBytes(this.config.ivSize);

    const derivedKey = await this.deriveKey(key, salt);
    const cipher = crypto.createCipheriv(
      this.config.algorithm,
      derivedKey,
      iv
    );

    const encrypted = Buffer.concat([
      cipher.update(data, 'utf8'),
      cipher.final()
    ]);

    return {
      encrypted: encrypted.toString('base64'),
      iv: iv.toString('base64'),
      salt: salt.toString('base64')
    };
  }

  async decrypt(
    encryptedData: EncryptedData,
    key: string
  ): Promise<string> {
    const salt = Buffer.from(encryptedData.salt, 'base64');
    const iv = Buffer.from(encryptedData.iv, 'base64');
    const encrypted = Buffer.from(encryptedData.encrypted, 'base64');

    const derivedKey = await this.deriveKey(key, salt);
    const decipher = crypto.createDecipheriv(
      this.config.algorithm,
      derivedKey,
      iv
    );

    return (
      decipher.update(encrypted) + decipher.final('utf8')
    );
  }

  private async deriveKey(
    key: string,
    salt: Buffer
  ): Promise<Buffer> {
    return new Promise((resolve, reject) => {
      crypto.pbkdf2(
        key,
        salt,
        this.config.iterations,
        this.config.keySize,
        'sha512',
        (err, derivedKey) => {
          if (err) {
            reject(err);
          } else {
            resolve(derivedKey);
          }
        }
      );
    });
  }
}

// Usage in repository
class SecureRepository {
  constructor(
    private readonly db: Database,
    private readonly encryptionService: EncryptionService
  ) {}

  async save(data: SensitiveData): Promise<void> {
    const encrypted = await this.encryptionService.encrypt(
      JSON.stringify(data),
      process.env.ENCRYPTION_KEY
    );

    await this.db.insert('secure_data', {
      ...encrypted,
      created_at: new Date()
    });
  }

  async findById(id: string): Promise<SensitiveData> {
    const encrypted = await this.db.findOne('secure_data', { id });
    if (!encrypted) {
      return null;
    }

    const decrypted = await this.encryptionService.decrypt(
      encrypted,
      process.env.ENCRYPTION_KEY
    );

    return JSON.parse(decrypted);
  }
}

2. Key Management

// key-management.service.ts
interface KeyConfig {
  algorithm: string;
  keySize: number;
  rotationPeriod: number;
}

class KeyManagementService {
  constructor(
    private readonly config: KeyConfig,
    private readonly keyRepository: KeyRepository
  ) {}

  async generateKeyPair(): Promise<KeyPair> {
    return new Promise((resolve, reject) => {
      crypto.generateKeyPair(
        this.config.algorithm,
        {
          modulusLength: this.config.keySize,
          publicKeyEncoding: {
            type: 'spki',
            format: 'pem'
          },
          privateKeyEncoding: {
            type: 'pkcs8',
            format: 'pem'
          }
        },
        (err, publicKey, privateKey) => {
          if (err) {
            reject(err);
          } else {
            resolve({ publicKey, privateKey });
          }
        }
      );
    });
  }

  async rotateKeys(): Promise<void> {
    const newKeyPair = await this.generateKeyPair();
    const now = new Date();

    await this.keyRepository.create({
      publicKey: newKeyPair.publicKey,
      privateKey: newKeyPair.privateKey,
      createdAt: now,
      expiresAt: new Date(now.getTime() + this.config.rotationPeriod)
    });

    // Mark old keys as expired
    await this.keyRepository.markExpired();
  }

  async getCurrentKey(): Promise<KeyPair> {
    const key = await this.keyRepository.findCurrent();
    if (!key) {
      throw new Error('No active key found');
    }

    return {
      publicKey: key.publicKey,
      privateKey: key.privateKey
    };
  }

  async getKeyById(id: string): Promise<KeyPair> {
    const key = await this.keyRepository.findById(id);
    if (!key) {
      throw new Error('Key not found');
    }

    return {
      publicKey: key.publicKey,
      privateKey: key.privateKey
    };
  }
}

// Usage in service
class SecureService {
  constructor(
    private readonly keyManagementService: KeyManagementService
  ) {}

  async sign(data: string): Promise<SignedData> {
    const keyPair = await this.keyManagementService.getCurrentKey();
    const signature = crypto.sign(
      'sha256',
      Buffer.from(data),
      keyPair.privateKey
    );

    return {
      data,
      signature: signature.toString('base64'),
      keyId: keyPair.id
    };
  }

  async verify(signedData: SignedData): Promise<boolean> {
    const keyPair = await this.keyManagementService.getKeyById(
      signedData.keyId
    );

    return crypto.verify(
      'sha256',
      Buffer.from(signedData.data),
      keyPair.publicKey,
      Buffer.from(signedData.signature, 'base64')
    );
  }
}

API Security Patterns

1. Rate Limiting

// rate-limiter.service.ts
interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
  blockDuration: number;
}

class RateLimiterService {
  constructor(
    private readonly config: RateLimitConfig,
    private readonly redis: Redis
  ) {}

  async checkRateLimit(
    key: string,
    identifier: string
  ): Promise<RateLimitResult> {
    const now = Date.now();
    const windowKey = `${key}:${identifier}:${Math.floor(
      now / this.config.windowMs
    )}`;

    const requests = await this.redis.incr(windowKey);
    if (requests === 1) {
      await this.redis.expire(windowKey, this.config.windowMs / 1000);
    }

    const isBlocked = await this.isBlocked(identifier);
    if (isBlocked) {
      return {
        allowed: false,
        remaining: 0,
        reset: await this.getBlockResetTime(identifier)
      };
    }

    if (requests > this.config.maxRequests) {
      await this.block(identifier);
      return {
        allowed: false,
        remaining: 0,
        reset: now + this.config.blockDuration
      };
    }

    return {
      allowed: true,
      remaining: Math.max(0, this.config.maxRequests - requests),
      reset: now + this.config.windowMs
    };
  }

  private async isBlocked(identifier: string): Promise<boolean> {
    const blockKey = `block:${identifier}`;
    return (await this.redis.exists(blockKey)) === 1;
  }

  private async block(identifier: string): Promise<void> {
    const blockKey = `block:${identifier}`;
    await this.redis.set(
      blockKey,
      '1',
      'EX',
      this.config.blockDuration / 1000
    );
  }

  private async getBlockResetTime(
    identifier: string
  ): Promise<number> {
    const blockKey = `block:${identifier}`;
    const ttl = await this.redis.ttl(blockKey);
    return Date.now() + ttl * 1000;
  }
}

// Usage in middleware
@Injectable()
class RateLimitMiddleware implements NestMiddleware {
  constructor(private readonly rateLimiter: RateLimiterService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const identifier = this.getIdentifier(req);
    const result = await this.rateLimiter.checkRateLimit(
      'api',
      identifier
    );

    res.setHeader('X-RateLimit-Limit', this.rateLimiter.config.maxRequests);
    res.setHeader('X-RateLimit-Remaining', result.remaining);
    res.setHeader('X-RateLimit-Reset', result.reset);

    if (!result.allowed) {
      res.status(429).json({
        error: 'Too Many Requests',
        retryAfter: Math.ceil((result.reset - Date.now()) / 1000)
      });
      return;
    }

    next();
  }

  private getIdentifier(req: Request): string {
    return req.ip || req.headers['x-forwarded-for'] || 'unknown';
  }
}

2. API Key Management

// api-key.service.ts
interface APIKeyConfig {
  keyLength: number;
  prefix: string;
  expiresIn: number;
}

class APIKeyService {
  constructor(
    private readonly config: APIKeyConfig,
    private readonly apiKeyRepository: APIKeyRepository
  ) {}

  async generateAPIKey(userId: string): Promise<APIKey> {
    const key = this.generateKey();
    const hashedKey = await this.hashKey(key);

    const apiKey = await this.apiKeyRepository.create({
      userId,
      keyHash: hashedKey,
      prefix: this.config.prefix,
      expiresAt: new Date(Date.now() + this.config.expiresIn)
    });

    return {
      id: apiKey.id,
      key,
      prefix: this.config.prefix,
      expiresAt: apiKey.expiresAt
    };
  }

  private generateKey(): string {
    return crypto
      .randomBytes(this.config.keyLength)
      .toString('hex')
      .toUpperCase();
  }

  private async hashKey(key: string): Promise<string> {
    return crypto
      .createHash('sha256')
      .update(key)
      .digest('hex');
  }

  async validateAPIKey(key: string): Promise<APIKeyValidation> {
    const prefix = key.substring(0, this.config.prefix.length);
    if (prefix !== this.config.prefix) {
      return { valid: false };
    }

    const hashedKey = await this.hashKey(key);
    const apiKey = await this.apiKeyRepository.findByHash(hashedKey);

    if (!apiKey) {
      return { valid: false };
    }

    if (apiKey.expiresAt < new Date()) {
      return { valid: false, expired: true };
    }

    return {
      valid: true,
      userId: apiKey.userId,
      permissions: apiKey.permissions
    };
  }

  async revokeAPIKey(keyId: string): Promise<void> {
    await this.apiKeyRepository.delete(keyId);
  }
}

// Usage in middleware
@Injectable()
class APIKeyMiddleware implements NestMiddleware {
  constructor(private readonly apiKeyService: APIKeyService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const apiKey = req.headers['x-api-key'];
    if (!apiKey) {
      res.status(401).json({ error: 'API key required' });
      return;
    }

    const validation = await this.apiKeyService.validateAPIKey(
      apiKey as string
    );

    if (!validation.valid) {
      res.status(401).json({
        error: validation.expired
          ? 'API key expired'
          : 'Invalid API key'
      });
      return;
    }

    req.user = {
      id: validation.userId,
      permissions: validation.permissions
    };

    next();
  }
}

Best Practices

1. Authentication

  • Use strong password policies
  • Implement MFA
  • Secure session management
  • Regular security audits

2. Authorization

  • Follow principle of least privilege
  • Implement role-based access
  • Use attribute-based access
  • Regular permission review

3. Encryption

  • Use strong encryption
  • Secure key management
  • Regular key rotation
  • Data classification

4. API Security

  • Implement rate limiting
  • Use API keys
  • Validate input
  • Monitor usage

Conclusion

Advanced security patterns enable building secure, reliable applications. By understanding and applying these patterns, developers can create robust security solutions that protect against modern threats.

Resources

WY

Cap

Senior Golang Backend & Web3 Developer with 10+ years of experience building scalable systems and blockchain solutions.

View Full Profile →