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/06 11:09] – [Jest Test] pradnyaauthentication_role-based_authorization_architecture [2026/01/09 08:30] (current) – [Install Swagger packages] pradnya
Line 97: Line 97:
 |Refresh Token|Session|7–30 days| |Refresh Token|Session|7–30 days|
  
-Roles are **NOT** stored inside JWT. +Roles are **NOT**  stored inside JWT.
  
 ====== Refresh-token implementation ====== ====== Refresh-token implementation ======
 +<code>
  
-<code> 
 src/ src/
 ┣ domain/ ┣ domain/
Line 144: Line 143:
  
 TTL determines refresh lifetime (e.g., **7 days**) TTL determines refresh lifetime (e.g., **7 days**)
- 
  
 ==== JWT Provider ==== ==== JWT Provider ====
  
-**jwt-provider.ts** +**jwt-provider.ts** <font 9px/inherit;;inherit;;inherit>ts</font>
- <font 9px/inherit;;inherit;;inherit>ts</font>+
  
 <code> <code>
- 
 import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
  
-const ACCESS_EXP = "15m"; +const ACCESS_EXP = "15m";
 const REFRESH_EXP = "7d"; const REFRESH_EXP = "7d";
  
-export function signAccessToken(user: any) {  +export function signAccessToken(user: any) { 
-  return jwt.sign(  +  return jwt.sign( 
-    {  +    { 
-      sub: user.id,  +      sub: user.id, 
-      roles: user.roles,  +      roles: user.roles, 
-      email: user.email  +      email: user.email 
-    },  +    }, 
-    process.env.JWT_ACCESS_SECRET!,  +    process.env.JWT_ACCESS_SECRET!, 
-    { expiresIn: ACCESS_EXP }  +    { expiresIn: ACCESS_EXP } 
-  ); +  );
 } }
  
-export function signRefreshToken(user: any, tokenId: string) {  +export function signRefreshToken(user: any, tokenId: string) { 
-  return jwt.sign(  +  return jwt.sign( 
-    {  +    { 
-      sub: user.id,  +      sub: user.id, 
-      tid: tokenId  +      tid: tokenId 
-    },  +    }, 
-    process.env.JWT_REFRESH_SECRET!,  +    process.env.JWT_REFRESH_SECRET!, 
-    { expiresIn: REFRESH_EXP }  +    { expiresIn: REFRESH_EXP } 
-  ); +  );
 } }
  
-export function verifyAccessToken(token: string) {  +export function verifyAccessToken(token: string) { 
-  return jwt.verify(token, process.env.JWT_ACCESS_SECRET!); +  return jwt.verify(token, process.env.JWT_ACCESS_SECRET!);
 } }
  
-export function verifyRefreshToken(token: string) {  +export function verifyRefreshToken(token: string) { 
-  return jwt.verify(token, process.env.JWT_REFRESH_SECRET!);  +  return jwt.verify(token, process.env.JWT_REFRESH_SECRET!); 
-+}
  
 JWT Provider JWT Provider
- 
  
 </code> </code>
Line 199: Line 194:
  <font 9px/inherit;;inherit;;inherit>ts</font>  <font 9px/inherit;;inherit;;inherit>ts</font>
  
-<code>import crypto from "crypto";  +<code> 
-export const generateTokenId = () => crypto.randomUUID(); +import crypto from "crypto"; 
 +export const generateTokenId = () => crypto.randomUUID();
  
 </code> </code>
  
-==== Auth Service —   Rotating Refresh Token Strategy   ====+==== Auth Service — Rotating Refresh Token Strategy ====
  
 auth-service.ts auth-service.ts
Line 211: Line 207:
  
 <code> <code>
- +import { 
-import {  +  storeRefreshToken, 
-  storeRefreshToken,  +  getRefreshToken, 
-  getRefreshToken,  +  deleteRefreshToken
-  deleteRefreshToken +
 } from "../../infrastructure/redis/refresh-token-repo"; } from "../../infrastructure/redis/refresh-token-repo";
  
-import {  +import { 
-  signAccessToken,  +  signAccessToken, 
-  signRefreshToken,  +  signRefreshToken, 
-  verifyRefreshToken +  verifyRefreshToken
 } from "../../infrastructure/security/jwt-provider"; } from "../../infrastructure/security/jwt-provider";
  
Line 228: Line 223:
 const REFRESH_TTL = 60 * 60 * 24 * 7; // 7 days const REFRESH_TTL = 60 * 60 * 24 * 7; // 7 days
  
-export async function login(user: any) { +export async function login(user: any) {
   const tokenId = generateTokenId();   const tokenId = generateTokenId();
  
   await storeRefreshToken(user.id, tokenId, REFRESH_TTL);   await storeRefreshToken(user.id, tokenId, REFRESH_TTL);
  
-  return {  +  return { 
-    accessToken: signAccessToken(user),  +    accessToken: signAccessToken(user), 
-    refreshToken: signRefreshToken(user, tokenId)  +    refreshToken: signRefreshToken(user, tokenId) 
-  }; +  };
 } }
  
-export async function refresh(refreshToken: string) { +export async function refresh(refreshToken: string) {
   const decoded: any = verifyRefreshToken(refreshToken);   const decoded: any = verifyRefreshToken(refreshToken);
  
   const storedTokenId = await getRefreshToken(decoded.sub);   const storedTokenId = await getRefreshToken(decoded.sub);
  
-  if (!storedTokenId || storedTokenId !== decoded.tid) +  if (!storedTokenId || storedTokenId !== decoded.tid)
     throw new Error("Refresh token invalid or rotated");     throw new Error("Refresh token invalid or rotated");
  
-  // ROTATE TOKEN +  // ROTATE TOKEN
   await deleteRefreshToken(decoded.sub);   await deleteRefreshToken(decoded.sub);
  
-  const newTokenId = generateTokenId(); +  const newTokenId = generateTokenId();
   await storeRefreshToken(decoded.sub, newTokenId, REFRESH_TTL);   await storeRefreshToken(decoded.sub, newTokenId, REFRESH_TTL);
  
-  return {  +  return { 
-    accessToken: signAccessToken({ id: decoded.sub }),  +    accessToken: signAccessToken({ id: decoded.sub }), 
-    refreshToken: signRefreshToken({ id: decoded.sub }, newTokenId)  +    refreshToken: signRefreshToken({ id: decoded.sub }, newTokenId) 
-  }; +  };
 } }
  
-export async function logout(userId: string) {  +export async function logout(userId: string) { 
-  await deleteRefreshToken(userId);  +  await deleteRefreshToken(userId); 
- +}
  
 </code> </code>
Line 272: Line 266:
 ts ts
 <code> <code>
- 
  
 import { login, refresh, logout } from "../../domain/services/auth-service"; import { login, refresh, logout } from "../../domain/services/auth-service";
  
-export async function loginHandler(req, res) { +export async function loginHandler(req, res) {
   const user = await req.services.userService.validate(req.body);   const user = await req.services.userService.validate(req.body);
  
   const tokens = await login(user);   const tokens = await login(user);
  
-  res  +  res 
-    .cookie("refreshToken", tokens.refreshToken, {  +    .cookie("refreshToken", tokens.refreshToken,
-      httpOnly: true,  +      httpOnly: true, 
-      secure: true,  +      secure: true, 
-      sameSite: "strict"  +      sameSite: "strict" 
-    })  +    }) 
-    .json({  +    .json({ 
-      accessToken: tokens.accessToken,  +      accessToken: tokens.accessToken, 
-      roles: user.roles  +      roles: user.roles 
-    }); +    });
 } }
  
-export async function refreshHandler(req, res) { +export async function refreshHandler(req, res) {
   const token = req.cookies.refreshToken;   const token = req.cookies.refreshToken;
  
   const tokens = await refresh(token);   const tokens = await refresh(token);
  
-  res.cookie("refreshToken", tokens.refreshToken, {  +  res.cookie("refreshToken", tokens.refreshToken,
-    httpOnly: true,  +    httpOnly: true, 
-    secure: true,  +    secure: true, 
-    sameSite: "strict" +    sameSite: "strict"
   });   });
  
-  res.json({ accessToken: tokens.accessToken }); +  res.json({ accessToken: tokens.accessToken });
 } }
  
-export async function logoutHandler(req, res) {  +export async function logoutHandler(req, res) { 
-  await logout(req.user.id);  +  await logout(req.user.id); 
-  res.clearCookie("refreshToken");  +  res.clearCookie("refreshToken"); 
-  res.sendStatus(204);  +  res.sendStatus(204); 
- +}
  
 </code> </code>
Line 351: Line 343:
  
 </code> </code>
- 
  
 ===== Logging + Correlation ===== ===== Logging + Correlation =====
Line 359: Line 350:
  
 <code> <code>
- 
 import pino from "pino"; import pino from "pino";
  
-export const logger = pino({  +export const logger = pino({ 
-  level: "info",  +  level: "info", 
-  transport: { target: "pino-pretty" }  +  transport: { target: "pino-pretty"
-});  +});
  
 </code> </code>
Line 373: Line 362:
  <font 9px/inherit;;inherit;;inherit>ts</font>  <font 9px/inherit;;inherit;;inherit>ts</font>
  
-<code>logger.info({ userId: user.id }, "refresh token rotated"); +<code> 
 +logger.info({ userId: user.id }, "refresh token rotated");
  
 </code> </code>
Line 379: Line 369:
 ===== Client — Reading Cookie ===== ===== Client — Reading Cookie =====
  
-Browser automatically sends HttpOnly cookie — **frontend CANNOT read it (secure)** +Browser automatically sends HttpOnly cookie — **frontend CANNOT read it (secure)** <font 9px/inherit;;inherit;;inherit>ts</font>
- <font 9px/inherit;;inherit;;inherit>ts</font>+
  
-<code>await fetch("/auth/refresh", { method: "POST", credentials: "include" }); +<code> 
 +await fetch("/auth/refresh", { method: "POST", credentials: "include" });
  
 </code> </code>
Line 560: 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.1767697792.txt.gz · Last modified: by pradnya