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
PGADMIN_DEFAULT_EMAIL=pgadmin@postgres.com
PGADMIN_DEFAULT_PASSWORD=pgadmin
Run the docker
docker-compose up -d
Check the containers status
docker ps
- 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
saurabh2k1
/
auth-service
Authentication & Authorization Microservice
Part 2 is coming...
Top comments (0)