User Tools

Site Tools


authentication_role-based_authorization_architecture

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
authentication_role-based_authorization_architecture [2026/01/05 07:35] – [Redis Caching Implementation] pradnyaauthentication_role-based_authorization_architecture [2026/01/09 08:30] (current) – [Install Swagger packages] pradnya
Line 98: Line 98:
  
 Roles are **NOT**  stored inside JWT. Roles are **NOT**  stored inside JWT.
 +
 +====== Refresh-token implementation ======
 +<code>
 +
 +src/
 +┣ domain/
 +┃ ┣ entities/
 +┃ ┃ ┗ **user.ts**
 +┃ ┗ services/
 +┃ ┗ auth-service.ts
 +┣ infrastructure/
 +┃ ┣ redis/
 +┃ ┃ ┗ **refresh-token-repo.ts**
 +┃** ┣ security/
 +┃ ┃ ┣ jwt-provider.ts
 +┃ ┃ ┗ token-utils.ts**
 +
 +</code>
 +
 +**Redis Refresh Token Store**
 +
 +refresh-token-store.ts
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +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}`);
 +}
 +
 +</code>
 +
 +TTL determines refresh lifetime (e.g., **7 days**)
 +
 +==== JWT Provider ====
 +
 +**jwt-provider.ts** <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +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
 +
 +</code>
 +
 +==== Token Utility (ID generator) ====
 +
 +token-utils.ts
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +import crypto from "crypto";
 +export const generateTokenId = () => crypto.randomUUID();
 +
 +</code>
 +
 +==== Auth Service — Rotating Refresh Token Strategy ====
 +
 +auth-service.ts
 +
 +ts
 +
 +<code>
 +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);
 +}
 +
 +</code>
 +
 +==== Auth Controller ====
 +
 +auth-controller.ts
 +
 +ts
 +<code>
 +
 +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);
 +}
 +
 +</code>
 +
 +==== Auth Middleware ====
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +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" });
 +  }
 +}
 +
 +</code>
 +
 +==== Jest Test ====
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +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);
 +});
 +});
 +
 +</code>
 +
 +===== Logging + Correlation =====
 +
 +logger.ts
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +import pino from "pino";
 +
 +export const logger = pino({
 +  level: "info",
 +  transport: { target: "pino-pretty" }
 +});
 +
 +</code>
 +
 +Add logs in auth-service:
 + <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +logger.info({ userId: user.id }, "refresh token rotated");
 +
 +</code>
 +
 +===== Client — Reading Cookie =====
 +
 +Browser automatically sends HttpOnly cookie — **frontend CANNOT read it (secure)** <font 9px/inherit;;inherit;;inherit>ts</font>
 +
 +<code>
 +await fetch("/auth/refresh", { method: "POST", credentials: "include" });
 +
 +</code>
  
 ==== Role Retrieval Strategy (Best Practice) ==== ==== Role Retrieval Strategy (Best Practice) ====
Line 111: Line 388:
 redis-client.ts redis-client.ts
  <font 9px/inherit;;inherit;;inherit>ts</font>  <font 9px/inherit;;inherit;;inherit>ts</font>
- 
 <code> <code>
 +
 import Redis from "ioredis"; import Redis from "ioredis";
  
Line 143: Line 420:
  
 </code> </code>
- 
  
 ===== Swagger Setup ===== ===== Swagger Setup =====
Line 250: Line 526:
  
 </code> </code>
- 
- 
- 
  
 ===== Jest Tests (Example) ===== ===== Jest Tests (Example) =====
Line 277: Line 550:
  
 </code> </code>
 +====== Enviornment Setup  ======
  
 +**VS Code + Node + TypeScript setup for microservices**
  
 +==== Initialize Node + TypeScript ====
 +
 +<code>
 +npm init -y
 +
 +</code>
 +
 +Install core dependencies:
 +
 +<code>
 +npm install express jsonwebtoken bcryptjs pino pino-pretty dotenv mysql2
 +
 +</code>
 +
 +Install dev dependencies:
 +
 +<code>
 +npm install -D typescript ts-node-dev @types/node @types/express @types/bcryptjs @types/jsonwebtoken
 +
 +</code>
 +
 +Install dependencies for Pino logger to setup directory / folder structure
 +
 +<code>npm install pino pino-pretty pino-multi-stream fs-extra
 +</code>
 +
 +
 +==== Install Swagger packages ====
 +
 +Install swagger packages on the root directory or directory with tsconfig
 +
 +<code>
 +npm install swagger-jsdoc swagger-ui-express
 +npm install -D @types/swagger-ui-express
 +
 +</code>
 +
 +<code>
 +npm install dotenv
 +
 +npm install bcryptjs
 +
 +npm install --save-dev @types/bcryptjs
 +
 +</code>
 +
 +install json web token
 +
 +<code>
 +npm install jsonwebtoken
 +npm install --save-dev @types/jsonwebtoken
 +
 +</code>
  
  
authentication_role-based_authorization_architecture.1767598544.txt.gz · Last modified: by pradnya