For a long time, my understanding of frontend security was basically:
Don't expose API keys.
Don't do something stupid with passwords.
Use HTTPS.
Done :))
Turns out...
absolutely not XD
While working on production code, I started noticing something uncomfortable.
A lot of security vulnerabilities don't look like security vulnerabilities.
They look like completely normal code.
A line displaying HTML.
A hidden admin button.
A form validator.
A token stored in the browser.
An environment variable.
An API request.
Nothing is flashing red.
VS Code isn't screaming.
The application works perfectly.
And that's exactly what makes some of these problems dangerous ._.
So this isn't going to be another:
"Here are 10 OWASP vulnerabilities you should memorize."
I want to show you something more useful:
how security changes the way you read ordinary frontend code.
And more importantly, for every problem we'll answer three questions:
- Why is this actually dangerous?
- What should we do instead?
- What should you learn so you can recognize it yourself?
Because telling someone:
"YOUR APPLICATION IS VULNERABLE!!!"
and then disappearing is not particularly helpful XD
Let's break some things.
Preferably theoretically.
1. The Browser Is Not Your Trusted Environment
This is probably the most important rule in this entire article.
If code reaches someone's browser...
they control that environment.
They can open DevTools.
Modify JavaScript.
Change HTML.
Modify requests.
Call your API directly.
Change values in browser storage.
Disable your frontend validation.
Send requests your UI never allows.
Your beautiful Angular interface is not a security boundary :')
This sounds obvious after someone says it.
But it has consequences everywhere.
Consider:
if (user.role === 'admin') {
showAdminPanel();
}
Perfectly reasonable frontend code.
But if your backend assumes:
"Well... only admins can see the button."
we have a problem.
An attacker doesn't need your button.
They need your endpoint.
If this exists:
DELETE /api/admin/repositories/123
they can attempt to call it directly.
Your carefully hidden button is sitting there feeling extremely secure while the attacker completely ignores it XD
So what should you do?
Use frontend authorization for user experience.
Use backend authorization for security.
The frontend can decide:
Should I display this action?
The backend must independently decide:
Is this authenticated user actually allowed to perform this action?
Every sensitive endpoint should verify authorization server-side.
Learn next
Study:
- Authentication vs authorization
- Role-Based Access Control (RBAC)
- Permission-based authorization
- HTTP status codes
401vs403 - Server-side authorization guards/middleware
If you're using NestJS, specifically learn Guards and authorization patterns.
2. Disabled Buttons Are Also Not Security XD
Suppose we have:
<button disabled>
Delete Everything
</button>
Attacker:
oh no :(
Anyway...
Open DevTools.
Remove disabled.
Or skip the interface completely.
Send the HTTP request manually.
The same applies to logic like:
if (!isAdmin) {
return;
}
Again:
Good UX.
Not sufficient security.
The browser belongs to the user.
Anything enforced exclusively there can potentially be modified or bypassed.
The rule
Whenever you write a frontend restriction, ask:
"What happens if someone completely ignores this frontend?"
If the answer is:
"They can delete the database."
we may have discovered a slight architectural issue :')
3. “But The Data Came From Our API!”
This one changed how I think about data.
Imagine:
this.http.get('/api/profile').subscribe(profile => {
this.bio = profile.bio;
});
Then somewhere:
<div [innerHTML]="bio"></div>
At first glance:
The backend sent it.
Therefore it's trusted.
Right?
Not necessarily.
Where did the backend get bio?
Maybe:
User
↓
Database
↓
API
↓
Frontend
↓
HTML
The data took a lovely vacation through your entire technology stack...
but nobody actually made it trustworthy XD
The database storing malicious input doesn't sanitize it.
JSON serialization doesn't sanitize it.
HTTPS doesn't sanitize it.
Your API returning it doesn't sanitize it.
This leads to a security concept worth remembering:
Trust the provenance, not the transport.
Ask:
Where did this data originally come from?
Not:
"Which API returned it?"
If users, third-party APIs, URL parameters, imported files, GitHub data, CMS content, or another untrusted source can influence the value, treat it accordingly.
4. XSS Is Much More Interesting Than alert("hacked")
Most beginner explanations of Cross-Site Scripting show:
<script>alert("hacked")</script>
And honestly...
that makes XSS look almost adorable XD
Nobody cares if an attacker can display a popup saying "hacked."
The real problem is that malicious JavaScript may execute inside your application's origin and user context.
Depending on the application and its defenses, that can potentially allow an attacker to:
- Read data accessible to JavaScript
- Manipulate the interface
- Make authenticated actions as the victim
- Read browser storage accessible to JavaScript
- Exfiltrate accessible information
- Display phishing interfaces inside your legitimate application
Imagine logging into a trusted website...
and the website itself shows you a fake:
Session expired. Please enter your password again.
Except that UI was injected by an attacker.
Suddenly alert("hacked") doesn't seem like a particularly useful demonstration :')
5. Learn to Think in Sources and Sinks
Here's a security concept I wish frontend tutorials introduced much earlier.
Source
Where potentially untrusted data enters.
Examples:
URL parameters
Form inputs
Third-party APIs
Database content originally supplied by users
GitHub/API responses
localStorage
postMessage
CMS content
Sink
Somewhere that data becomes dangerous depending on how it's used.
For example:
<div [innerHTML]="content"></div>
The important question becomes:
SOURCE
↓
transformations
↓
more transformations
↓
SINK
Can an attacker control the source?
If yes:
What happens when their input reaches the sink?
That way of thinking is far more useful than memorizing a list of dangerous functions.
6. Your Framework Is Probably Protecting You More Than You Realize
Modern frameworks provide useful security defaults.
For example, Angular escapes/interprets interpolated values safely in normal templates.
Something like:
<p>{{ username }}</p>
is fundamentally different from deliberately injecting arbitrary HTML.
Frameworks are trying to help.
Then sometimes developers do this:
bypassSecurityTrustHtml(content)
Angular:
I don't trust this HTML.
Developer:
Don't worry bro.
Angular:
...okay ._.
XD
The name bypassSecurityTrustHtml is practically Angular asking you to sign a waiver.
There are legitimate situations where trusted HTML needs special handling.
But bypassing framework security means you have accepted responsibility for establishing that the value is actually safe.
Prevention
Prefer normal framework rendering whenever possible.
If you genuinely need user-controlled rich HTML, use a well-maintained sanitizer designed for that purpose and configure it for your threat model rather than trying to remove suspicious strings yourself.
Do NOT invent:
html.replace('<script>', '')
and declare victory XD
Attack payloads have considerably more creativity than that.
Learn next
Study:
- Cross-Site Scripting (XSS)
- DOM-based XSS
- Output encoding
- HTML sanitization
- Angular security model / React escaping behavior
- OWASP XSS Prevention Cheat Sheet
- DOMPurify or an equivalent maintained sanitizer when arbitrary HTML must be rendered
7. Frontend Validation Is Not Security Validation
You build a beautiful Angular form:
name: new FormControl('', [
Validators.required,
Validators.maxLength(100)
])
Fantastic.
Users cannot submit names longer than 100 characters.
Except...
attackers are under no contractual obligation to use your Angular form XD
They can directly send:
POST /api/users
with whatever payload they want.
Frontend validation is still important.
It gives immediate feedback.
Reduces accidental invalid requests.
Makes forms pleasant to use.
But:
Client validation improves user experience. Server validation establishes trust.
Your backend must validate the request again.
Types.
Lengths.
Formats.
Allowed values.
Required properties.
Unexpected properties where relevant.
Business constraints.
And validation should happen before untrusted data reaches sensitive operations.
Learn next
Study:
- DTO validation
- Schema validation
- Input validation
- Allowlisting vs blocklisting
- NestJS
ValidationPipe class-validator- OWASP Input Validation Cheat Sheet
8. Let's Talk About localStorage
Ah yes.
The internet's favourite security debate :')
You'll often hear:
NEVER STORE TOKENS IN LOCALSTORAGE!!!!
And then:
COOKIES ARE SECURE!!!!
Security is rarely that simple.
localStorage is accessible to JavaScript running on the same origin.
Therefore, if malicious JavaScript successfully executes through an XSS vulnerability, values accessible through localStorage may also become accessible to that script.
An HttpOnly cookie, by contrast, cannot be directly read through JavaScript.
That's useful protection against token theft through JavaScript.
But cookies introduce considerations of their own.
For authentication cookies, you need to understand things such as:
HttpOnlySecureSameSite- CSRF
- Cookie scope
- Session expiration
- Session invalidation
The lesson isn't:
localStorage bad.
cookies good.
The lesson is:
Understand what you're protecting against.
That's threat modeling.
Questions worth asking
If authentication credentials are stored here:
- Can JavaScript access them?
- What happens during an XSS attack?
- Can another origin cause authenticated requests?
- How does logout invalidate the session?
- What happens when credentials expire?
- Can compromised credentials be revoked?
Those questions lead to better architecture than memorizing Twitter security advice XD
Learn next
Study:
- Cookie-based authentication
- Session authentication
-
HttpOnly,Secure, andSameSite - CSRF
- XSS
- Token rotation
- Session invalidation
- OWASP Session Management Cheat Sheet
9. Your .env File Does Not Make Frontend Secrets Secret
This one hurts because .env feels extremely secure.
Look at it.
It even has a dot at the beginning.
Very mysterious :0
Suppose:
SUPER_SECRET_API_KEY=abc123
Then your frontend build includes that value.
Congratulations.
Your secret is now...
public :')
If the browser needs a value to execute your application, assume the user can obtain that value.
Minification doesn't change that.
Obfuscation doesn't create a security boundary.
Renaming:
SECRET_API_KEY
to:
x7Q_a19
does not defeat someone determined to inspect your application XD
Important distinction
Not every API key is necessarily secret.
Some services intentionally provide publishable client keys designed to exist in browser applications.
The important question is:
What authority does possession of this value provide?
If the key grants privileged server-side access, signs trusted requests, bypasses authorization, or gives access to private infrastructure...
it does not belong in frontend code.
Prevention
Keep actual secrets server-side.
Frontend:
Browser
↓
Your Backend
↓
Privileged External Service
The backend keeps the secret and performs authorization before accessing privileged services.
Learn next
Study:
- Public vs secret API keys
- Environment variables in frontend build systems
- Backend-for-Frontend patterns
- Secret management
- API key restrictions
- Least privilege
And remember:
Anything shipped to the browser should be treated as observable by the user.
10. Rate Limiting Is Not Disabling a Button for Five Seconds
Suppose your frontend does:
if (requestInProgress) {
return;
}
Useful.
But that's UI throttling.
Not security rate limiting.
Again...
the attacker doesn't need your UI :')
They can directly automate requests against the endpoint.
This matters particularly for operations like:
- Login attempts
- Password resets
- Expensive searches
- Email sending
- File processing
- Administrative actions
- Expensive third-party API calls
Without rate limiting, one endpoint can potentially become a resource exhaustion or abuse mechanism.
And authentication alone doesn't magically solve this.
Authenticated accounts can be:
- Compromised
- Automated
- Buggy
- Malicious
Prevention
Rate limiting belongs at a trusted server or infrastructure boundary.
Depending on the architecture, that could mean:
- Application middleware/guards
- API gateway
- Reverse proxy
- CDN/WAF
- Distributed rate limiter
For distributed systems, you also need to think about whether counters are shared across instances.
Learn next
Study:
- Rate limiting
- Token bucket
- Sliding-window algorithms
- HTTP
429 Too Many Requests - NestJS throttling
- Reverse proxies
- Distributed rate limiting
11. Third-Party JavaScript Is Part of Your Security Boundary
Analytics.
Chat widgets.
A/B testing.
Payment SDKs.
Random npm packages.
Convenient scripts copied from somewhere at 2 AM because Stack Overflow said it works XD
Every script executing in your page deserves scrutiny.
If third-party JavaScript executes with access to your application's origin, you're trusting more than your own code.
That doesn't mean:
NEVER USE THIRD-PARTY JAVASCRIPT.
It means:
Know who you're trusting.
This is also why dependency security matters.
Your application can contain perfectly secure code...
and still inherit vulnerabilities through its software supply chain.
Prevention
- Minimize unnecessary dependencies.
- Keep dependencies updated deliberately.
- Review high-risk dependencies.
- Avoid casually loading remote scripts.
- Use integrity/security mechanisms where appropriate.
- Monitor dependency advisories.
- Remove abandoned packages when feasible.
Learn next
Study:
- Software supply-chain security
- Dependency auditing
- npm audit and its limitations
- Subresource Integrity (SRI)
- Content Security Policy
- Lockfiles
- OWASP Software Supply Chain guidance
12. Content Security Policy: Assume Something Eventually Goes Wrong
Imagine you've done everything correctly.
Then six months later someone introduces an XSS vulnerability.
Defense-in-depth asks:
Can we make exploitation harder even after the first defense fails?
That's where browser security mechanisms such as Content Security Policy (CSP) become interesting.
A well-designed CSP can restrict which scripts, styles, frames, and other resources the browser is allowed to execute or load.
It isn't:
CSP = true
XD
A weak policy can provide little protection.
A badly configured policy can break your application.
And CSP should not replace proper output handling or sanitization.
It's another layer.
Other important browser-side defenses include appropriate security headers and policies around framing, MIME handling, referrers, permissions, and transport.
Learn next
Study:
- Content Security Policy
- CSP nonces and hashes
- Security headers
- Clickjacking
frame-ancestors- HSTS
- Referrer Policy
- Permissions Policy
MDN and OWASP are excellent places to learn these properly.
So... How Do I Actually Think About Frontend Security?
After discovering all of this, it can become tempting to look at every line like:
IS THIS A VULNERABILITY??? :0
That's not particularly productive either XD
Instead, I've started using a simpler mental model.
Follow the data.
Whenever important data enters the system, ask:
1. Where did it originate?
User?
URL?
Database?
GitHub?
Third-party API?
Browser storage?
CMS?
2. Who controls it?
Us?
An administrator?
Any authenticated user?
Anyone on the internet?
Another service?
3. Where do we start trusting it?
Did the backend validate it?
Did we verify authorization?
Was HTML sanitized?
Are we merely assuming it's trustworthy because it came from our own API?
4. Where does it go?
HTML?
Database?
URL?
DOM?
Logs?
External API?
Another user?
5. What happens if an attacker controls it?
This is the fun question :')
Could they:
- Execute JavaScript?
- Modify another user's data?
- Perform an admin operation?
- Leak credentials?
- Exhaust resources?
- Inject misleading content?
- Access something outside their permissions?
6. Where is the actual security boundary?
If your answer is:
"The button is hidden."
we need to talk XD
My Frontend Security Checklist
When reviewing frontend-heavy applications now, these are some of the questions I'd ask.
Authentication & Authorization
- Are sensitive operations authorized on the backend?
- Are sessions invalidated correctly?
- Are credentials stored according to an explicit threat model?
- Are admin endpoints independently protected?
Input & Output
- Is untrusted input validated server-side?
- Is dynamic HTML actually necessary?
- Are framework security protections being bypassed?
- Is dangerous output encoded or sanitized appropriately?
Browser
- Are authentication cookies configured correctly?
- Is there a sensible CSP?
- Are sensitive values unnecessarily exposed to JavaScript?
- Can the application be embedded where it shouldn't be?
API
- Are expensive/sensitive endpoints rate-limited?
- Does the backend assume the frontend already validated something?
- Can users modify IDs or parameters to access resources belonging to someone else?
- Are authorization checks performed against the requested resource?
Secrets
- Does any real secret reach the frontend bundle?
- Are external service credentials scoped to minimum required privileges?
- Can exposed client keys be restricted by origin, API, quota, or permissions?
Dependencies
- Do we actually need every dependency?
- Are critical packages maintained?
- Are third-party scripts necessary?
- Are security advisories being monitored?
You don't need to memorize every vulnerability ever discovered.
You need to develop the habit of asking:
What assumption am I making about trust here?
What Should a Beginner Actually Study?
If you're reading this thinking:
Cool.
Everything is dangerous now.
Thanks :')
Don't randomly consume cybersecurity content for six months.
Start with web security fundamentals.
I'd learn roughly in this order:
1. HTTP fundamentals
Understand:
- Requests and responses
- Headers
- Cookies
- Origins
- CORS
- HTTPS
2. Authentication & authorization
Understand:
- Sessions
- Cookies
- Tokens
- RBAC
- Server-side authorization
3. XSS
Learn:
- Stored XSS
- Reflected XSS
- DOM-based XSS
- Sources and sinks
- Encoding vs sanitization
4. CSRF
Especially if you're using cookie-based authentication.
5. Input validation
Understand why both frontend and backend validation exist.
6. Browser security
Learn:
- Same-Origin Policy
- CORS
- CSP
- Secure cookies
- Browser storage
7. API security
Learn:
- Authorization
- Rate limiting
- Object-level access control
- Validation
- Abuse prevention
8. OWASP
Don't just memorize the OWASP Top 10.
Use it as a map of categories you should understand.
Two resources I'd keep returning to are:
OWASP Cheat Sheet Series
https://cheatsheetseries.owasp.org/
and:
MDN Web Security
https://developer.mozilla.org/en-US/docs/Web/Security
For practical exercises, PortSwigger's Web Security Academy is also extremely useful:
https://portswigger.net/web-security
Read something.
Break something in a legal training environment.
Understand why it broke.
Fix it.
That's much more effective than memorizing vulnerability names.
The Biggest Thing That Changed for Me
A while ago, when reviewing frontend code, I'd mostly ask:
Does this work?
Then:
Is this clean?
Is this maintainable?
Is this performant?
I'm slowly learning that there's another question that needs to exist alongside all of them:
What happens if somebody intentionally uses this in a way we didn't expect?
That's security thinking.
A form isn't only something a user fills out.
It's an input boundary.
An API response isn't automatically trusted data.
A hidden button isn't authorization.
A disabled button isn't protection.
An environment variable isn't automatically a secret.
A framework security escape hatch isn't something to casually bypass.
A successful request doesn't mean the request should have been allowed.
And a feature working exactly as intended doesn't mean it's safe.
That's probably the uncomfortable part of learning security.
Your code can be:
✓ Working
✓ Clean
✓ Tested
✓ Fast
✓ Beautiful
and still be:
✗ Please don't deploy this XD
Final Thought
I don't think becoming better at application security means memorizing every attack payload.
And I definitely don't think every frontend developer needs to become a penetration tester.
But if you're building applications that handle real users, real accounts, real data, or real permissions...
you need to understand trust.
Where it begins.
Where it ends.
And where you're accidentally creating it.
Because many security vulnerabilities aren't created by developers writing obviously dangerous code.
They're created by completely reasonable assumptions:
"The frontend already checked it."
"Only admins can see this button."
"It came from our API."
"It's inside
.env.""The framework handles security."
"The user can't send that value."
Individually, each sentence sounds harmless.
Until someone asks:
"...but what if that's not true?"
._.
That's the question I'm trying to get better at asking.
I'm still learning security.
Still finding assumptions in my own code.
Still reading documentation.
Still discovering browser behavior that makes me go:
"WAIT... it can do WHAT?!" :0
And probably still going to write something someday that a security engineer looks at and quietly closes their laptop XD
But now, when I read frontend code, I don't only see components, requests, forms, and state.
I see trust boundaries.
And once you start seeing those...
normal frontend code starts looking very different :))



Top comments (0)