User Tools

Site Tools


authentication_role-based_authorization_architecture

Authentication + Role-Based Authorization Architecture

Overview

This BFF handles:

  • Authentication
  • Role & permission resolution (from MySQL)
  • Token lifecycle
  • Secure API gateway behavior
  • UI-friendly role exposure
  • Backend authorization enforcement

Frontend uses:

  • Roles → feature toggling
  • Backend → strict enforcement

Clean Architecture Directory Structure

src/
 ┣ api/
 ┃ ┣ controllers/
 ┃ ┃ ┣ auth-controller.ts
 ┃ ┃ ┗ user-controller.ts
 ┃ ┣ middleware/
 ┃ ┃ ┣ auth-middleware.ts
 ┃ ┃ ┣ authorize.ts
 ┃ ┃ ┗ error-middleware.ts
 ┃ ┗ routes/
 ┃    ┣ auth-routes.ts
 ┃    ┗ user-routes.ts
 ┣ domain/
 ┃ ┣ auth/
 ┃ ┃ ┣ auth-service.ts
 ┃ ┃ ┗ role-service.ts
 ┃ ┣ user/
 ┃ ┃ ┗ user-service.ts
 ┗ infrastructure/
  ┣ db/
  ┃ ┣ mysql-connection.ts
  ┃ ┗ user-role-repo.ts
  ┣ cache/
  ┃ ┗ redis-client.ts
  ┣ auth/
  ┃ ┣ jwt-provider.ts
  ┃ ┗ token-store.ts
  ┣ logging/
  ┃ ┗ logger.ts
  ┗ config/
     ┗ env.ts

Authentication & Authorization Flow

Flow Summary

Login → verify user → load roles → cache → JWT issued → user DTO returned

Every request → verify JWT → get roles (cache first) → enforce policy

API Endpoints

POST /auth/login

{  "accessToken": "xxx",
   "refreshToken": "yyy",
   "expiresIn": 900,
   "user": {
             "id": "u123",
             "displayName": "John",
             "roles": ["BOOKING_CREATE"],
             "permissions": ["booking.create"]
            }
}

POST /auth/refresh

Returns fresh token + roles.

GET /auth/me

Returns latest user profile.

JWT Strategy

TokenPurposeExpiry
Access TokenIdentity15 mins
Refresh TokenSession7–30 days

Roles are NOT stored inside JWT.

Refresh-token implementation

src/
┣ domain/
┃ ┣ entities/
┃ ┃ ┗ **user.ts**
┃ ┗ services/
┃ ┗ auth-service.ts
┣ infrastructure/
┃ ┣ redis/
┃ ┃ ┗ **refresh-token-repo.ts**
┃** ┣ security/
┃ ┃ ┣ jwt-provider.ts
┃ ┃ ┗ token-utils.ts**

Redis Refresh Token Store

refresh-token-store.ts ts

import { redis } from "./redis-client";

const REFRESH_PREFIX = "refresh:user:";

export async function storeRefreshToken(userId: string, tokenId: string, ttlSeconds: number) {
  const key = `${REFRESH_PREFIX}${userId}`;
  await redis.set(key, tokenId, "EX", ttlSeconds);
}

export async function getRefreshToken(userId: string) {
  return redis.get(`${REFRESH_PREFIX}${userId}`);
}

export async function deleteRefreshToken(userId: string) {
  return redis.del(`${REFRESH_PREFIX}${userId}`);
}

TTL determines refresh lifetime (e.g., 7 days)

JWT Provider

jwt-provider.ts ts

import jwt from "jsonwebtoken";

const ACCESS_EXP = "15m";
const REFRESH_EXP = "7d";

export function signAccessToken(user: any) {
  return jwt.sign(
    {
      sub: user.id,
      roles: user.roles,
      email: user.email
    },
    process.env.JWT_ACCESS_SECRET!,
    { expiresIn: ACCESS_EXP }
  );
}

export function signRefreshToken(user: any, tokenId: string) {
  return jwt.sign(
    {
      sub: user.id,
      tid: tokenId
    },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: REFRESH_EXP }
  );
}

export function verifyAccessToken(token: string) {
  return jwt.verify(token, process.env.JWT_ACCESS_SECRET!);
}

export function verifyRefreshToken(token: string) {
  return jwt.verify(token, process.env.JWT_REFRESH_SECRET!);
}

JWT Provider

Token Utility (ID generator)

token-utils.ts ts

import crypto from "crypto";
export const generateTokenId = () => crypto.randomUUID();

Auth Service — Rotating Refresh Token Strategy

auth-service.ts

ts

import {
  storeRefreshToken,
  getRefreshToken,
  deleteRefreshToken
} from "../../infrastructure/redis/refresh-token-repo";

import {
  signAccessToken,
  signRefreshToken,
  verifyRefreshToken
} from "../../infrastructure/security/jwt-provider";

import { generateTokenId } from "../../infrastructure/security/token-utils";

const REFRESH_TTL = 60 * 60 * 24 * 7; // 7 days

export async function login(user: any) {
  const tokenId = generateTokenId();

  await storeRefreshToken(user.id, tokenId, REFRESH_TTL);

  return {
    accessToken: signAccessToken(user),
    refreshToken: signRefreshToken(user, tokenId)
  };
}

export async function refresh(refreshToken: string) {
  const decoded: any = verifyRefreshToken(refreshToken);

  const storedTokenId = await getRefreshToken(decoded.sub);

  if (!storedTokenId || storedTokenId !== decoded.tid)
    throw new Error("Refresh token invalid or rotated");

  // ROTATE TOKEN
  await deleteRefreshToken(decoded.sub);

  const newTokenId = generateTokenId();
  await storeRefreshToken(decoded.sub, newTokenId, REFRESH_TTL);

  return {
    accessToken: signAccessToken({ id: decoded.sub }),
    refreshToken: signRefreshToken({ id: decoded.sub }, newTokenId)
  };
}

export async function logout(userId: string) {
  await deleteRefreshToken(userId);
}

Auth Controller

auth-controller.ts

ts

import { login, refresh, logout } from "../../domain/services/auth-service";

export async function loginHandler(req, res) {
  const user = await req.services.userService.validate(req.body);

  const tokens = await login(user);

  res
    .cookie("refreshToken", tokens.refreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: "strict"
    })
    .json({
      accessToken: tokens.accessToken,
      roles: user.roles
    });
}

export async function refreshHandler(req, res) {
  const token = req.cookies.refreshToken;

  const tokens = await refresh(token);

  res.cookie("refreshToken", tokens.refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "strict"
  });

  res.json({ accessToken: tokens.accessToken });
}

export async function logoutHandler(req, res) {
  await logout(req.user.id);
  res.clearCookie("refreshToken");
  res.sendStatus(204);
}

Auth Middleware

ts

import { verifyAccessToken } from "../../infrastructure/security/jwt-provider";

export function authMiddleware(req, res, next) {
  try {
    const token = req.headers.authorization?.split(" ")[1];
    const decoded: any = verifyAccessToken(token);
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ message: "Unauthorized" });
  }
}

Jest Test

ts

describe("refresh flow", () => {
it("rotates refresh tokens", async () => {
  const loginRes = await login({ id: "1" });

    const refreshRes = await refresh(loginRes.refreshToken);

    expect(refreshRes.accessToken).toBeDefined();
  expect(refreshRes.refreshToken).not.toBe(loginRes.refreshToken);
});
});

Logging + Correlation

logger.ts ts

import pino from "pino";

export const logger = pino({
  level: "info",
  transport: { target: "pino-pretty" }
});

Add logs in auth-service: ts

logger.info({ userId: user.id }, "refresh token rotated");

Browser automatically sends HttpOnly cookie — frontend CANNOT read it (secure) ts

await fetch("/auth/refresh", { method: "POST", credentials: "include" });

Role Retrieval Strategy (Best Practice)

SourcePurpose
MySQLSource of truth
RedisPerformance cache

Cache duration = 5–10 minutes

Redis Caching Implementation

redis-client.ts ts

import Redis from "ioredis";

export const redis = new Redis({
  host: process.env.REDIS_HOST,
  ttl: 600 // 10 mins
});

Logging with Pino + Trace IDs ts

import pino from "pino";
import { v4 as uuid } from "uuid";

export const logger = pino({
level: process.env.LOG_LEVEL || "info",
transport: process.env.NODE_ENV === "development"
  ? { target: "pino-pretty" }
  : undefined
});

export function withTraceId(req, res, next) {
req.traceId = req.headers["x-trace-id"] || uuid();
logger.info({ traceId: req.traceId }, "Request started");
next();
}

Swagger Setup

swagger.ts ts

import swaggerUi from "swagger-ui-express";
import swaggerJsDoc from "swagger-jsdoc";

const spec = swaggerJsDoc({
definition: {
  openapi: "3.0.0",
  info: { title: "BFF API", version: "1.0.0" }
},
apis: ["./src/api/routes/*.ts"]
});

export default (app) => {
app.use("/docs", swaggerUi.serve, swaggerUi.setup(spec));
};

JWT Provider

jwt-provider.ts ts

import jwt from "jsonwebtoken";

export function signAccessToken(userId: string) {
return jwt.sign({ sub: userId }, process.env.JWT_SECRET!, {
  expiresIn: "15m"
});
}

export function verifyAccessToken(token: string) {
return jwt.verify(token, process.env.JWT_SECRET!);
}

Auth Middleware

auth-middleware.ts ts

import { verifyAccessToken } from "../../infrastructure/auth/jwt-provider";

export function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.sendStatus(401);

  try {
  req.user = verifyAccessToken(token);
  next();
} catch {
  return res.sendStatus(401);
}
}

Role Enforcement Middleware

authorize.ts ts

export function requireRole(role: string) {
return (req, res, next) => {
  if (!req.user?.roles?.includes(role)) {
    return res.sendStatus(403);
  }
  next();
};
}

Role Caching Layer

role-service.ts ts

import { redis } from "../../infrastructure/cache/redis-client";
import { userRoleRepo } from "../../infrastructure/db/user-role-repo";

export async function getUserRoles(userId: string) {
const cacheKey = `roles:${userId}`;

  const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);

  const roles = await userRoleRepo.getRoles(userId);

  await redis.set(cacheKey, JSON.stringify(roles), "EX", 600);

  return roles;
}

Jest Tests (Example)

ts

describe("role cache", () => {
it("returns cached role", async () => {
  await redis.set("roles:u1", JSON.stringify(["ADMIN"]));
  expect(await getUserRoles("u1")).toContain("ADMIN");
});
});

Swagger Example

yaml

securitySchemes:
bearerAuth:
  type: http
  scheme: bearer

Enviornment Setup

VS Code + Node + TypeScript setup for microservices

Initialize Node + TypeScript

npm init -y

Install core dependencies:

npm install express jsonwebtoken bcryptjs pino pino-pretty dotenv mysql2

Install dev dependencies:

npm install -D typescript ts-node-dev @types/node @types/express @types/bcryptjs @types/jsonwebtoken

Install dependencies for Pino logger to setup directory / folder structure

npm install pino pino-pretty pino-multi-stream fs-extra

Install Swagger packages

Install swagger packages on the root directory or directory with tsconfig

npm install swagger-jsdoc swagger-ui-express
npm install -D @types/swagger-ui-express
npm install dotenv

npm install bcryptjs

npm install --save-dev @types/bcryptjs

install json web token

npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken
authentication_role-based_authorization_architecture.txt · Last modified: by pradnya