In today’s digital landscape, securing user authentication is more critical than ever. Whether you're developing a SaaS platform, an enterprise application, or a multi-tenant system, a robust authentication and authorization microservice can help centralize user management and enforce security best practices. In this blog post, we will walk through the complete development of an authentication and authorization microservice using Node.js, Express, and PostgreSQL, incorporating JWT-based authentication, role-based access control (RBAC), and Two-Factor Authentication (2FA) using TOTP (Time-Based One-Time Passwords). We will also ensure industry-grade security practices, including password hashing, input validation, and secure token management. By the end of this guide, you'll have a production-ready microservice that can be integrated with multiple applications, serving as a reliable identity provider. Let’s get started! 🚀

It is part of series:

  • Part 1

  • Part 2

  • Part 3

Authentication & Authorization:

Mechanisms: Username/password, TOTP.

Services: User registration, authenticator registration, login, token issuance (JWT or OAuth2), and role-based access control.

Super Admin: One dedicated user with elevated privileges.

Deployment:

Containerization: Docker support for easier deployment and orchestration (e.g., Kubernetes).

Security:
Industry best practices, such as secure password storage
Initialize Project

Let's start

1. Create directory & initiate npm

mkdir auth-service
cd auth-service
npm init -y
Install typescript and other development dependency
npm install typescript ts-node @types/node --save-dev
tsc --init
Install express framework and database ORM (TypeORM) & pg
npm install express typeorm pg dotenv
npm install @types/express --save-dev
To get it work, open tsconfig.json and add following settings in compilerOptions
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strictPropertyInitialization": false,

2. Install Postgres & pgAdmin using docker-compose

create file docker-compose.yml in root and copy the following content

version: "3.8"
services:
  db:
    image: postgres
    container_name: local_pgdb
    restart: always
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - local_pgdata:/var/lib/postgresql/data
  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin4_container
    restart: always
    ports:
      - "8888:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL}
      - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
    volumes:
      - pgadmin-data:/var/lib/pgadmin

volumes:
  local_pgdata:
  pgadmin-data:

Create .env file in root with your credentials (Don't use the given details)

DB_HOST=localhost
DB_PORT=5432

DB_USERNAME=postgres
DB_PASSWORD=postgres

[email protected]
PGADMIN_DEFAULT_PASSWORD=pgadmin
Run the docker
docker-compose up -d
Check the containers status
docker ps

docker ps output

  • you can access the pgAdmin using url http://localhost:8888 
  • login using your credentials and create server connection 
  • host will be container name(eg.local_pgdb) of Postgres container

3. ORM Configuration

create data-source.ts file in src folder with following code

import { DataSource } from "typeorm";
import * as dotenv from 'dotenv';
dotenv.config();

export const AppDataSource = new DataSource({
  type: "postgres", 
  host: "localhost",
  port: 5432,
  username: process.env.DB_USERNAME || "postgres",
  password: process.env.DB_PASSWORD || "postgres",
  database: process.env.DB_DATABASE || "postgres",
  synchronize: true,    // make false for production
  logging: false,
  entities: ["src/entity/**/*.ts"],
  migrations: ["src/migration/**/*.ts"],
  subscribers: ["src/subscriber/**/*.ts"],
});

AppDataSource.initialize()
    .then(() => {
    console.log("Data Source has been initialized!");
  })
  .catch((err) => {
    console.error("Error during Data Source initialization:", err);
  });
Create following folder structure in src folder
  • src/entity
  • src/migration
  • src/subscriber

3.1 Create Database Schema

Create a Userentity in src/entity/User.ts:
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  username: string;

  @Column()
  passwordHash: string;

  @Column({ default: 'user' })
  role: string;

  @Column({ nullable: true })
  totpSecret: string;

  @CreateDateColumn()
  createdAt: Date;
}
Create migration file CreateUserTable in src/migration
npx typeorm-ts-node-commonjs -d ./src/data-source.ts migration:generate ./src/migration/CreateUserTable
Run the migration using following command
npx typeorm-ts-node-commonjs -d src/data-source.ts migration:run

Install other libraries

npm install bcrypt speakeasy qrcode jsonwebtoken express-validator
npm install @types/bcrypt @types/speakeasy  @types/qrcode @types/jsonwebtoken

4. Startup file

Create index.ts file in src folder with following initial code:

import express from 'express';
import { AppDataSource } from './data-source';

const app = express();

app.get('/health', (req, res) => {
  res.send('Auth Microservice is running');
});

AppDataSource.initialize()
    .then(() => {
    console.log("Data Source has been initialized!");
    app.listen(3000, () => {
      console.log('Auth Microservice is running on port 3000');
    });

  })
  .catch((err) => {
    console.error("Error during Data Source initialization:", err);
  });
Create start script in package.json as below:
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1", 
    "start": "ts-node ./src/index.ts"
  },

run the application using npm run start
You can access the code from repository

GitHub logo saurabh2k1 / auth-service

Authentication & Authorization Microservice


Part 2 is coming...