JSONP Explained: How It Works and Why It Matters for Web Security
If you work in application security, you've probably encountered JSONP while studying the Same-Origin Policy (SOP), cross-origin attacks, or legacy web applications.
At first glance, JSONP looks simple:
JSONP = JSON wrapped inside a JavaScript callback.
But to understand its security implications properly, you need to understand what the browser actually does with the response.
This article breaks down JSONP from the browser's perspective and explains why it worked as a cross-origin data-sharing mechanism, how it differs from CORS, and where the security risks come from.
1. Start With the Same-Origin Policy
Let's say your application is hosted at:
https://www.example.com
and the API is hosted at:
https://api.example.com
These are different origins because the hostname is different.
If JavaScript on www.example.com performs:
fetch("https://api.example.com/user");
the browser may make the request, but JavaScript cannot freely read the response unless the API explicitly permits the requesting origin through CORS.
Conceptually:
www.example.com
|
| fetch()
v
api.example.com
|
| response
v
Browser
|
X
|
JavaScript cannot read
the response unless
CORS permits it
This is an important distinction:
The Same-Origin Policy does not prevent all cross-origin requests. It restricts what the requesting origin is allowed to read or do with the response.
2. The <script> Tag Is Special
Browsers have historically allowed web pages to load JavaScript from other origins.
For example:
<script src="https://cdn.example.com/app.js"></script>
Your application might be:
https://www.example.com
while the JavaScript is hosted at:
https://cdn.example.com
The browser downloads the script and executes it.
So the browser has a mechanism that effectively allows:
www.example.com
|
| <script src="...">
v
cdn.example.com
This behavior is the primitive that JSONP takes advantage of.
3. What Happens If We Put JSON Directly in a <script>?
Suppose an API returns:
{
"id": 123,
"name": "Amar"
}
Now imagine requesting it using:
<script src="https://api.example.com/user"></script>
The browser expects the response to be JavaScript.
But the server returned JSON:
{
"id": 123,
"name": "Amar"
}
That's not the JSONP pattern we want.
So JSONP changes the response.
Instead of returning:
{
"id": 123,
"name": "Amar"
}
the server returns:
callback({
"id": 123,
"name": "Amar"
});
Now the response is valid JavaScript.
That's the core idea behind JSONP.
4. The Callback Is the Key
Suppose the client defines this function:
function handleUser(data) {
console.log(data.name);
}
The client then loads:
<script src="https://api.example.com/user?callback=handleUser"></script>
The server sees:
callback=handleUser
and generates:
handleUser({
"id": 123,
"name": "Amar"
});
The browser receives the response and executes it.
The flow is:
Browser
|
| GET /user?callback=handleUser
|
v
API Server
|
| Generates JavaScript
|
| handleUser({...})
v
Browser JavaScript Engine
|
| Executes
v
handleUser(data)
The JSON data is now available to the page's JavaScript.
5. A Complete JSONP Example
Client
<script>
function receiveUser(data) {
console.log("User ID:", data.id);
console.log("User name:", data.name);
}
</script>
<script src="https://api.example.com/user?callback=receiveUser"></script>
HTTP Request
GET /user?callback=receiveUser HTTP/1.1
Host: api.example.com
Server Response
HTTP/1.1 200 OK
Content-Type: application/javascript
receiveUser({
"id": 123,
"name": "Amar"
});
Browser
The browser executes:
receiveUser({
id: 123,
name: "Amar"
});
Therefore:
data.name
is available inside receiveUser().
6. Why Can JavaScript Access the Data?
This is the most important concept to understand.
The browser isn't saying:
"This is a cross-origin JSON response, so I'll allow JavaScript to read it."
Instead, the browser sees:
<script src="https://api.example.com/..."></script>
and treats the response as a script resource.
The response is therefore executed as JavaScript.
So JSONP is better understood as:
Cross-origin script inclusion where the server converts the requested data into executable JavaScript.
This distinction is extremely important from a security perspective.
JSONP isn't simply:
"Cross-origin JSON reading"
It is:
"Cross-origin JavaScript execution containing JSON data"
7. Does JSONP Actually Bypass the Same-Origin Policy?
It's better to say that JSONP takes advantage of a browser feature that is permitted by the Same-Origin Policy model.
Browsers intentionally allow certain cross-origin mechanisms.
For example:
| Mechanism | Cross-origin behavior |
|---|---|
<img> |
Can load cross-origin resources |
<script> |
Can load and execute cross-origin scripts |
<link> |
Can load certain cross-origin resources |
| HTML forms | Can submit cross-origin requests |
fetch() |
Cross-origin response access is controlled by CORS |
| XHR | Cross-origin response access is controlled by CORS |
So the misconception to avoid is:
"SOP blocks all cross-origin communication."
It doesn't.
The browser has always permitted certain forms of cross-origin interaction.
JSONP uses the <script> mechanism.
8. JSONP Request Flow
The complete flow looks like this:
JSONP FLOW
┌──────────────────────┐
│ Browser │
│ │
│ function foo(data) │
└──────────┬───────────┘
│
│ <script src=
│ https://api.example.com/
│ user?callback=foo>
│
▼
┌──────────────────────┐
│ API Server │
│ │
│ Reads callback=foo │
└──────────┬───────────┘
│
│ Generates
│ JavaScript
│
▼
┌──────────────────────┐
│ HTTP Response │
│ │
│ foo({ │
│ "id": 123, │
│ "name": "Amar" │
│ }) │
└──────────┬───────────┘
│
│ Browser executes
▼
┌──────────────────────┐
│ foo(data) │
│ │
│ JavaScript receives │
│ the JSON data │
└──────────────────────┘
9. Why JSONP Was Useful
Before modern CORS support became the standard way to allow controlled cross-origin API access, developers needed ways for applications hosted on different domains to exchange data.
JSONP provided a relatively simple solution:
Application
|
| <script>
v
Remote API
|
| JavaScript response
v
Application
It worked particularly well for public APIs and simple GET-based data retrieval.
However, it came with significant limitations and security risks.
10. JSONP Is GET-Based
Because JSONP relies on:
<script src="..."></script>
the request is essentially a GET request.
JSONP therefore isn't appropriate for normal API operations such as:
POST
PUT
PATCH
DELETE
This is another reason modern applications use CORS and standard HTTP APIs instead.
11. The Major Security Problem: JSONP Executes Code
With a normal JSON API:
{
"name": "Amar"
}
the response is data.
With JSONP:
callback({
"name": "Amar"
});
the response is executable JavaScript.
That changes the security model considerably.
The server is effectively telling the browser:
"Execute this JavaScript."
This is why JSONP endpoints need to be treated carefully.
12. Potential Cross-Origin Data Disclosure
Imagine a sensitive API:
https://bank.example/api/account?callback=foo
which returns:
foo({
"balance": 500000,
"accountNumber": "..."
});
An attacker might attempt to embed:
<script src="https://bank.example/api/account?callback=foo"></script>
If the victim's browser sends the necessary authentication credentials and the endpoint is vulnerable to this type of cross-origin access, the returned data could become available to the attacker's page.
Conceptually:
Victim Browser
|
| Authenticated request
v
Bank API
|
| foo(sensitive data)
v
Attacker-controlled page
|
v
foo(data)
However, this is not automatically exploitable in every modern environment.
Actual exploitability depends on factors such as:
Authentication mechanism
Cookie behavior
SameSitecookie settingsCSRF protections
Whether authentication credentials are automatically sent
Whether the endpoint supports JSONP
Whether the response is actually executable
Browser behavior
Therefore:
Don't conclude that "JSONP automatically allows an attacker to steal authenticated data."
The surrounding authentication and browser security controls matter.
13. Callback Injection
Another major JSONP security issue is unsafe handling of the callback parameter.
Suppose the server receives:
?callback=foo
and generates:
foo({...});
If the application simply concatenates the callback value into the response without validation, the callback parameter becomes a potential JavaScript injection point.
Conceptually:
callback=foo;ATTACKER_CODE//
could potentially result in something resembling:
foo({...});ATTACKER_CODE//({...});
The exact exploitability depends on:
Server-side parsing
Output encoding
Framework behavior
JavaScript syntax
Response construction
Validation
Therefore, callback parameters should be strictly validated.
A safer implementation should restrict the callback to an appropriate JavaScript identifier format rather than accepting arbitrary JavaScript syntax.
For example, conceptually:
Allowed:
foo
foo123
handleUser
myCallback
Rejected:
arbitrary JavaScript
quotes
semicolons
comments
expressions
function calls
Better still, applications should avoid exposing unnecessary user-controlled callback functionality.
14. JSONP and Authentication
This is where JSONP becomes particularly interesting from an AppSec perspective.
Consider:
https://victim.example/api/profile?callback=foo
If the endpoint returns user-specific information based on browser credentials, you need to ask:
What credentials does the browser send?
For example:
Cookies
Authorization headers
Client certificates
Other authentication mechanisms
For traditional cookie-based authentication, modern browser cookie policies such as:
SameSite
Secure
HttpOnly
can significantly affect whether a cross-site request is authenticated.
This means a JSONP vulnerability should always be analyzed in the context of the actual authentication mechanism, rather than assuming credentials will automatically accompany the request.
15. JSONP vs CORS
This is one of the most important comparisons.
JSONP
Browser
|
| <script>
v
API
|
| JavaScript
| callback({...})
v
Browser
|
| Executes response
v
Application
The API effectively says:
"Here is executable JavaScript."
CORS
Browser
|
| fetch()
v
API
|
| Access-Control-Allow-Origin
v
Browser
|
| Exposes response to JS
v
fetch().then(...)
The API effectively says:
"This origin is allowed to read my response."
That is a much cleaner security model.
16. Why CORS Replaced JSONP
CORS allows APIs to explicitly control which origins can read their responses.
For example:
Access-Control-Allow-Origin: https://www.example.com
The browser can then allow JavaScript from that origin to access the response.
CORS also works with normal HTTP methods and APIs:
GET
POST
PUT
PATCH
DELETE
JSONP is limited primarily to the <script>/GET model.
Modern APIs therefore generally use:
fetch()
+
CORS
rather than:
<script>
+
JSONP
17. A Useful Mental Model
Don't think of JSONP as:
"JSON that bypasses CORS."
Think of it as:
A server-generated JavaScript program containing JSON data, loaded through a cross-origin
<script>element.
That mental model makes the behavior much easier to understand.
JSON API
{
"name": "Amar"
}
↓
DATA
JSONP API
foo({
"name": "Amar"
});
↓
JAVASCRIPT
+
DATA
The difference is fundamental.
18. Security Review Checklist for JSONP
If you discover a JSONP endpoint during a penetration test, examine:
1. Is JSONP actually enabled?
Look for:
?callback=
?jsonp=
?cb=
?callbackFunction=
2. Is the endpoint sensitive?
Determine whether it returns:
User information
Account information
PII
Tokens
Internal data
Application configuration
Business information
3. Does authentication apply?
Determine:
Is the endpoint authenticated?
What authentication mechanism is used?
Are credentials automatically sent cross-site?
Are cookies SameSite protected?
4. Is the callback attacker-controlled?
Test whether:
callback=attackerControlledValue
is reflected into executable JavaScript.
5. Is callback validation implemented?
Check whether the server restricts callback names to a safe syntax.
6. What is the response Content-Type?
Ideally JSONP responses are explicitly treated as JavaScript:
Content-Type: application/javascript
Don't rely on MIME sniffing behavior.
7. Is JSONP actually necessary?
For modern applications, ask whether the functionality can be replaced with:
fetch()
+
CORS
19. The Most Important Takeaway
The entire JSONP mechanism can be summarized as:
JSONP
Client defines:
function foo(data) { ... }
↓
Client requests:
<script src=
"https://api.example.com/data?callback=foo">
↓
Server receives:
callback=foo
↓
Server generates:
foo({
"name": "Amar"
});
↓
Browser treats response
as JavaScript
↓
Browser executes:
foo(data)
↓
Client JavaScript
receives the data
The critical security concept is:
JSONP works because
<script>resources can be loaded cross-origin and their contents are executed as JavaScript. The server wraps JSON data inside a callback so the response becomes executable JavaScript.
That is also why JSONP has security risks that a normal JSON API does not have.
Final Mental Model
If you remember only this:
SOP
│
├── Cross-origin fetch/XHR
│ └── Response reading controlled by CORS
│
└── Cross-origin <script>
└── Script can be loaded/executed
│
▼
JSONP
│
▼
callback(JSON data)
│
▼
JavaScript executes
JSONP is therefore a legacy cross-origin data-sharing technique that turns data into executable JavaScript.
For application security, the two areas worth investigating most closely are JSONP-based sensitive-data exposure and callback-parameter injection.

