User Tools

Site Tools


authentication_role-based_authorization_architecture

This is an old revision of the document!


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.

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
authentication_role-based_authorization_architecture.1767598516.txt.gz · Last modified: by pradnya