Why Frontend Authorization Is Not Enough: Lessons From a Security Audit
During a security audit of a SaaS app, several “admin-only” actions were only hidden in the React UI. The API still accepted the requests if you called them directly. That is not authorization — that is obscurity.
What Frontend Checks Are Good For
- Better UX (do not show actions you cannot use)
- Fewer confused clicks
- Faster feedback when a plan lacks a feature
They are not a security boundary. Any user can open DevTools, replay a request, or call the API with a script.
The Failure Pattern
// Frontend only — insufficient
{user.role === 'admin' && (
<button onClick={deleteOrg}>Delete organization</button>
)}
// Missing on the API
app.delete('/api/orgs/:id', async (req, res) => {
await db.orgs.delete(req.params.id); // no role check
res.status(204).end();
});
What Proper Authorization Looks Like
- Authenticate the request (session/JWT)
- Load membership / role for the target resource
- Authorize the specific action (RBAC or policy)
- Perform the mutation
- Audit-log sensitive actions
app.delete('/api/orgs/:id', requireAuth, async (req, res) => {
const membership = await getMembership(req.user.id, req.params.id);
if (!membership || membership.role !== 'owner') {
return res.status(403).json({ error: 'forbidden' });
}
await db.orgs.delete(req.params.id);
await audit.log('org.delete', { actor: req.user.id, orgId: req.params.id });
res.status(204).end();
});
Audit Techniques That Caught Issues
- Map every privileged UI action to its API route
- Replay those routes as a lower-privilege user
- Try IDOR: change resource IDs in URLs and bodies
- Check background jobs that accept user-controlled IDs
- Verify plan/feature gates on the server, not only in React
Tests You Should Automate
member cannot delete org
member cannot change billing
owner can invite admin
cross-tenant IDOR returns 404/403
feature-gated endpoint returns 402/403 on free plan
Final Thoughts
Frontend authorization improves UX. Server authorization protects the business. If a control matters, the API must refuse unauthorized callers — every time.
Written by A.M. Rinas · Published August 29, 2026