How to Solve Application Authorization Issues with Tech Due Diligence
During a critical pre-launch technical audit, we uncovered serious authorization vulnerabilities that could have compromised user data and system integrity. Here's what we found and how we fixed it.
The Discovery
Our client was preparing to launch their SaaS platform when they engaged us for a technical due diligence review. What we found was alarming:
Critical Issues Identified
-
No Role-Based Access Control (RBAC)
- All users had admin-level access
- No distinction between user roles
- Potential for unauthorized data access
-
Missing Authorization Checks
- API endpoints accessible without proper verification
- Resource access not validated
- Direct object reference vulnerabilities
-
Insecure Session Management
- Sessions never expired
- No session invalidation on logout
- Tokens stored in local storage without encryption
-
Insufficient Input Validation
- No validation on user inputs
- SQL injection vulnerabilities
- Cross-site scripting (XSS) risks
The Solution
We implemented a comprehensive authorization system following industry best practices:
1. Implementing RBAC
// Define user roles enum UserRole { SUPER_ADMIN = 'super_admin', ADMIN = 'admin', MANAGER = 'manager', USER = 'user', GUEST = 'guest' } // Define permissions enum Permission { CREATE_USER = 'create:user', READ_USER = 'read:user', UPDATE_USER = 'update:user', DELETE_USER = 'delete:user', MANAGE_ROLES = 'manage:roles' } // Role-permission mapping const rolePermissions: Record<UserRole, Permission[]> = { [UserRole.SUPER_ADMIN]: [ Permission.CREATE_USER, Permission.READ_USER, Permission.UPDATE_USER, Permission.DELETE_USER, Permission.MANAGE_ROLES ], [UserRole.ADMIN]: [ Permission.CREATE_USER, Permission.READ_USER, Permission.UPDATE_USER ], [UserRole.MANAGER]: [ Permission.READ_USER, Permission.UPDATE_USER ], [UserRole.USER]: [ Permission.READ_USER ], [UserRole.GUEST]: [] };
2. API Authorization Middleware
// Authorization middleware function authorize(requiredPermissions: Permission[]) { return async (req: Request, res: Response, next: NextFunction) => { const user = req.user; // from authentication middleware if (!user) { return res.status(401).json({ error: 'Unauthorized' }); } const userPermissions = rolePermissions[user.role]; const hasPermission = requiredPermissions.every( permission => userPermissions.includes(permission) ); if (!hasPermission) { return res.status(403).json({ error: 'Forbidden' }); } next(); }; } // Usage in routes router.post('/users', authenticate(), authorize([Permission.CREATE_USER]), createUserHandler ); router.delete('/users/:id', authenticate(), authorize([Permission.DELETE_USER]), deleteUserHandler );
3. Resource-Level Authorization
// Check if user owns the resource or has admin rights async function canAccessResource( userId: string, resourceId: string, userRole: UserRole ): Promise<boolean> { // Admins can access all resources if ([UserRole.SUPER_ADMIN, UserRole.ADMIN].includes(userRole)) { return true; } // Check if user owns the resource const resource = await db.resources.findById(resourceId); return resource.ownerId === userId; } // API implementation router.get('/documents/:id', authenticate(), async (req, res) => { const { id } = req.params; const user = req.user; const hasAccess = await canAccessResource(user.id, id, user.role); if (!hasAccess) { return res.status(403).json({ error: 'Access denied' }); } const document = await db.documents.findById(id); res.json(document); });
4. Secure Session Management
import jwt from 'jsonwebtoken'; import { v4 as uuidv4 } from 'uuid'; // Generate secure tokens function generateTokens(userId: string, role: UserRole) { const accessToken = jwt.sign( { userId, role, type: 'access' }, process.env.JWT_SECRET!, { expiresIn: '15m' } ); const refreshToken = jwt.sign( { userId, tokenId: uuidv4(), type: 'refresh' }, process.env.JWT_REFRESH_SECRET!, { expiresIn: '7d' } ); return { accessToken, refreshToken }; } // Logout implementation router.post('/logout', authenticate(), async (req, res) => { const user = req.user; // Invalidate refresh token await db.refreshTokens.delete({ userId: user.id }); // Clear cookies res.clearCookie('accessToken'); res.clearCookie('refreshToken'); res.json({ message: 'Logged out successfully' }); });
5. Input Validation
import { z } from 'zod'; // Define validation schemas const createUserSchema = z.object({ email: z.string().email(), name: z.string().min(2).max(100), role: z.nativeEnum(UserRole), password: z.string() .min(8) .regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/) }); // Validation middleware function validateBody(schema: z.ZodSchema) { return (req: Request, res: Response, next: NextFunction) => { try { req.body = schema.parse(req.body); next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation failed', details: error.errors }); } next(error); } }; } // Usage router.post('/users', authenticate(), authorize([Permission.CREATE_USER]), validateBody(createUserSchema), createUserHandler );
Security Testing
We implemented comprehensive security testing:
1. Unit Tests for Authorization
describe('Authorization Middleware', () => { it('should deny access without required permission', async () => { const req = { user: { id: '123', role: UserRole.USER } }; const res = { status: jest.fn().mockReturnThis(), json: jest.fn() }; const middleware = authorize([Permission.DELETE_USER]); await middleware(req, res, jest.fn()); expect(res.status).toHaveBeenCalledWith(403); expect(res.json).toHaveBeenCalledWith({ error: 'Forbidden' }); }); });
2. Penetration Testing
We conducted thorough penetration testing:
- Attempted privilege escalation
- Tested for IDOR vulnerabilities
- Verified token expiration
- Checked session invalidation
3. Security Audit Checklist
✅ Authentication implemented correctly ✅ Authorization checks on all endpoints ✅ RBAC system in place ✅ Input validation on all inputs ✅ Secure session management ✅ SQL injection prevention ✅ XSS protection ✅ CSRF tokens implemented ✅ Rate limiting configured ✅ Security headers set ✅ HTTPS enforced ✅ Secrets properly managed
Results
After implementing these fixes:
- Zero critical vulnerabilities in follow-up audit
- 100% authorization coverage on all endpoints
- Passed security compliance requirements
- Successful product launch with confidence
- No security incidents post-launch
Best Practices Implemented
- Principle of Least Privilege: Users get minimum required permissions
- Defense in Depth: Multiple layers of security
- Secure by Default: Restrictive permissions, explicit grants
- Regular Audits: Quarterly security reviews
- Security Training: Team educated on secure coding
Key Lessons
For Startups
- Don't skip security: It's harder to retrofit than build in
- Hire experts early: Security audits before launch save money
- Test thoroughly: Automated and manual security testing
- Document everything: Clear security policies and procedures
For Developers
- Never trust user input: Always validate and sanitize
- Implement authorization checks: On every endpoint
- Use established libraries: Don't roll your own crypto
- Keep dependencies updated: Regular security patches
- Log security events: For auditing and monitoring
Tools We Used
- OWASP ZAP: Vulnerability scanning
- Burp Suite: Penetration testing
- SonarQube: Static code analysis
- Snyk: Dependency vulnerability checking
- JWT: Secure token management
- bcrypt: Password hashing
- Helmet.js: Security headers
Conclusion
Technical due diligence uncovered critical authorization issues that could have been catastrophic post-launch. Proper authentication and authorization aren't optional—they're fundamental to application security.
Action Items for Your Application
- Conduct security audit before launch
- Implement RBAC system
- Add authorization middleware to all routes
- Validate and sanitize all inputs
- Implement secure session management
- Test authorization thoroughly
- Document security policies
- Train team on security best practices
Don't wait for a security breach—act now.

