Enhancing Security with Hash ID in NestJS

Introduction

Hashids is a small open-source library that generates short, unique, non-sequential ids from numbers. It helps secure the system by hiding the real database IDs, preventing users from guessing or scraping data via URLs.

Advantages:

  • Security: Hides real IDs, preventing exposure of data structure and the total number of records.
  • Two-way transformation: Allows easy encoding and decoding without requiring storage in the database.
  • High customization: Supports minimum length configuration and utilizes a distinct salt value to guarantee the generated strings are unique to your system.
  • No collisions: The exact same ID and salt value will consistently produce the identical unique string.

Limitations:

  • Not true cryptography: Hashids does not employ strong encryption algorithms, meaning someone with the salt and the algorithm can still reverse the string. Therefore, do not use it to secure highly sensitive data.
  • Dependency on Salt: If you lose or alter your Salt string, all previously encoded IDs cannot be decoded back to their original form accurately.

At this stage, you have several deployment approaches:

  • Most databases natively support UUIDs, which you can use directly to eliminate the need for hashing IDs. However, adopting UUID version 4 takes up 36 characters and using it for every ID over a long period with massive data volumes will consume significant storage space.
  • If you are building the schema from scratch and prefer a simpler alternative, you can define the ID field as a string and use hashids to encode it prior to inserting new records.
  • If your system is already operational with auto-incrementing IDs and you want to boost security without modifying the database schema, you can perform ID encoding and decoding during database query operations. This approach introduces extra computational overhead for every API and this article will guide you through implementing this specific workflow.

Detail

Update the .env file with a salt value used for encoding and decoding

HASHID_SALT = <HASHID_SALT>

Create the file src/service/hash-id.service.ts

import {Injectable} from '@nestjs/common'
import {default as Hashids} from 'hashids'
import {EnvironmentService} from './environment.service'

@Injectable()
export class HashIdService {
  private MIN_LENGTH: number = 4
  private hashids: Hashids

  constructor(private readonly configService: ConfigService) {
    const hashIdSalt = this.configService.get<string>('HASHID_SALT') || ''
    this.hashids = new Hashids(hashIdSalt, this.MIN_LENGTH)
  }

  encodeId(id: number | string) {
    const idStr = typeof id === 'number' ? id.toString() : id
    return this.hashids.encode(idStr)
  }

  decodeId(hash: string) {
    const decoded = this.hashids.decode(hash)
    return decoded.length > 0 ? decoded[0] : null
  }
}

Using hashids is quite straightforward as shown above, so I will not explain much further, you can adjust values like MIN_LENGTH as needed.

Create the file src/controller/test.controller.ts to test the implementation of hashids

import {HashIdService} from 'src/service/hash-id.service'
import {PostService} from 'src/service/post.service'

@Controller('test')
export class TestController {
  constructor(
    private postService: PostService,
    private hashIdService: HashIdService
  ) {}
  @Get('post')
  getPost() {
    const posts = this.postService.getPost()
    return posts.map(p => ({...p, id: this.hashIdService.encodeId(p.id)}))
  }

  @Put('post/:id')
  editPost(@Param('id') id: string) {
    const realId = this.hashIdService.decodeId(id)
    return {id, realId}
  }
}


Afterwards, remember to register these services and controllers in app.module.ts. You can verify the final outcome.


Happy coding!

See more articles here.

Comments

Popular posts from this blog

All Practice Series

Kubernetes Deployment for Zero Downtime

Deploying a NodeJS Server on Google Kubernetes Engine

Setting up Kubernetes Dashboard with Kind

Sitemap

React Practice Series

Monitoring with cAdvisor, Prometheus and Grafana on Docker

DevOps Practice Series

A Handy Guide to Using Dynamic Import in JavaScript

Using Kafka with Docker and NodeJS