YeboNa Middleware
Authentication and request handling middleware.
Authentication Middleware
Location: api/src/middleware/auth.ts
authMiddleware
Verifies JWT and attaches user to request.
typescript
import { Response, NextFunction } from 'express';
import { verifyToken, extractToken, AuthRequest } from '../utils/jwt';
export const authMiddleware = (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
try {
const token = extractToken(req);
if (!token) {
return res.status(401).json({
success: false,
error: 'Authentication required',
});
}
const payload = verifyToken(token);
if (!payload) {
return res.status(401).json({
success: false,
error: 'Invalid or expired token',
});
}
// Attach user to request
req.user = payload;
next();
} catch (error) {
return res.status(401).json({
success: false,
error: 'Authentication failed',
});
}
};Usage:
typescript
router.get('/me', authMiddleware, getCurrentUser);optionalAuth
For routes where auth is optional.
typescript
export const optionalAuth = (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
try {
const token = extractToken(req);
if (token) {
const payload = verifyToken(token);
if (payload) {
req.user = payload;
}
}
next();
} catch (error) {
// Continue without auth
next();
}
};Rate Limiting
Location: api/src/app.ts
Authentication Rate Limiter
Stricter limits for auth endpoints.
typescript
import rateLimit from 'express-rate-limit';
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 20, // 20 requests
message: {
success: false,
error: 'Too many auth attempts, please try again later'
},
validate: { trustProxy: false }
});
app.use('/api/auth', authLimiter, authRoutes);Public Rate Limiter
General API endpoints.
typescript
const publicLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests
message: {
success: false,
error: 'Too many requests, please try again later'
},
validate: { trustProxy: false }
});
app.use('/api/providers', publicLimiter, providerRoutes);CORS Configuration
typescript
import cors from 'cors';
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
}));Request Parsing
typescript
app.use(express.json());Trust Proxy
Required for Cloud Run / load balancers:
typescript
app.set('trust proxy', 1);Error Handler
typescript
app.use((
err: Error,
_req: express.Request,
res: express.Response,
_next: express.NextFunction
) => {
console.error('Unhandled error:', err);
res.status(500).json({
success: false,
error: config.env === 'production'
? 'Internal server error'
: err.message
});
});404 Handler
typescript
app.use((_req, res) => {
res.status(404).json({
success: false,
error: 'Not found'
});
});JWT Utilities
Location: api/src/utils/jwt.ts
Token Generation
typescript
export function generateToken(payload: TokenPayload): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRY });
}
export async function generateTokenPair(payload: TokenPayload): Promise<{
accessToken: string;
refreshToken: string;
}> {
const accessToken = generateToken(payload);
const refreshToken = crypto.randomUUID();
// Store refresh token
await db.refreshTokens.create({
token: refreshToken,
userId: payload.userId,
phoneNumber: payload.phoneNumber,
expiresAt: new Date(Date.now() + REFRESH_EXPIRY_MS)
});
return { accessToken, refreshToken };
}Token Verification
typescript
export function verifyToken(token: string): TokenPayload | null {
try {
return jwt.verify(token, JWT_SECRET) as TokenPayload;
} catch {
return null;
}
}
export async function verifyRefreshToken(token: string): Promise<{
userId: string;
phoneNumber: string;
} | null> {
const record = await db.refreshTokens.findByToken(token);
if (!record || new Date(record.expires_at) < new Date()) {
return null;
}
return {
userId: record.user_id,
phoneNumber: record.phone_number
};
}Token Extraction
typescript
export function extractToken(req: AuthRequest): string | null {
const authHeader = req.headers.authorization;
if (authHeader?.startsWith('Bearer ')) {
return authHeader.substring(7);
}
return null;
}Token Revocation
typescript
export async function revokeRefreshToken(token: string): Promise<boolean> {
return db.refreshTokens.delete(token);
}
export async function revokeAllUserTokens(userId: string): Promise<number> {
return db.refreshTokens.deleteAllForUser(userId);
}Middleware Stack Order
typescript
// app.ts
// 1. Trust proxy
app.set('trust proxy', 1);
// 2. CORS
app.use(cors({ ... }));
// 3. Body parser
app.use(express.json());
// 4. Routes with rate limiting
app.use('/api/auth', authLimiter, authRoutes);
app.use('/api/users', publicLimiter, userRoutes);
// ...
// 5. 404 handler
app.use(notFoundHandler);
// 6. Error handler (last)
app.use(errorHandler);