authentication_role-based_authorization_architecture
This is an old revision of the document!
Table of Contents
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
| Token | Purpose | Expiry |
|---|---|---|
| Access Token | Identity | 15 mins |
| Refresh Token | Session | 7–30 days |
Roles are NOT stored inside JWT.
Role Retrieval Strategy (Best Practice)
| Source | Purpose |
|---|---|
| MySQL | Source of truth |
| Redis | Performance 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.1767598544.txt.gz · Last modified: by pradnya
