# Why shouldn't JSONP​ be used?

Source: https://tpiros.dev/blog/why-jsonp-shouldnt-be-used

[In a previous article](https://fullstack-developer.academy/what-is-cors/), we covered CORS requests: why they happen and how to deal with them. Sometimes when CORS comes up, people reach for JSONP (JSON with Padding) as a workaround. Let's talk about why that's a bad idea.

# CORS 101 - a quick recap

When a web application requests a resource from a domain you don't control, the browser might block you with `Failed to load resource: Origin * is not allowed by Access-Control-Allow-Origin.`. The browser is refusing access to that resource (typically an API endpoint).

> For more information read the article titled "[What is CORS?](https://fullstack-developer.academy/what-is-cors/)"

# An example implementation

Let's set up a scenario that triggers the CORS problem. Two Node.js files will do it: an API server and an HTTP server.

```javascript
// api.js
const express = require('express');
const app = express();
const port = 3000;

const apiHandler = (request, response) => {
  response.json({ message: 'Hello World!' });
};

app.get('/api/hello', apiHandler);

app.listen(port, () => console.info(`API up on port ${port}.`));

// http-server.js
const http = require('http');
const path = require('path');
const fs = require('fs');
const port = 8080;

const httpServerHandler = (request, response) => {
  response.writeHead(200);
  response.write(fs.readFileSync(path.join(`${process.cwd()}/index.html`)));
  response.end();
};

http.createServer(httpServerHandler).listen(port);
console.log(`HTTP Server is up on port ${port}`);
```

> Please note that this HTTP server is bare-bones. It also uses `fs.readFileSync`, a _blocking_ method you shouldn't use in production. For real work, grab an npm package for your web server.

Now we've got two Node.js processes: an API server on port 3000 and an HTTP server on port 8080. Under CORS rules, these count as separate origins, so the browser will block cross-requests.

Here's a simple HTML file that tries to make an AJAX request via jQuery:

```html
<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>JSONP</title>
    <meta name="author" content="Tamas Piros" />
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  </head>

  <body>
    <h1 id="message"></h1>
    <script>
      $.ajax({
        url: 'http://localhost:3000/api/hello',
      }).done((data) => console.log(data));
    </script>
  </body>
</html>
```

Open the browser and navigate to `localhost:8080`. You'll hit the CORS message. That's the expected behaviour.

# JSONP

Let's see what JSONP does here. Notice something interesting in our `index.html`: we're already pulling in a resource from a different domain (the jQuery library from a CDN).

That works because browsers let you load resources through the `<script>` tag without CORS restrictions.

JSONP exploits this. We modify our API server to return data wrapped in a function call. That's the "padding" in JSONP. The browser interprets the function call and we get our data.

Here's the updated API:

```javascript
const apiHandler = (request, response) => {
  response.jsonp({ message: 'Hello World!' });
};
```

And the updated AJAX request in `index.html`:

```javascript
$.ajax({
  url: 'http://localhost:3000/api/hello',
  dataType: 'jsonp',
}).done((data) => console.log(data));
```

Refresh the browser and the console shows the message. We can take it further and push the message into the `<h1>` tag:

```javascript
$.ajax({
  url: 'http://localhost:3000/api/hello',
  dataType: 'jsonp',
}).done(
  (data) => (document.getElementById('message').textContent = data.message)
);
```

It works. But this article is about why you shouldn't do this.

## HTTP GET only

JSONP only works with `HTTP GET` requests. No other methods. This makes sense: the `<script>` tag can only fire `HTTP GET` requests, and that's exactly what we've exploited.

## No error handling

With normal AJAX, you can inspect the error body returned by the API. With JSONP, you get either a CORS error or a 404. Debugging becomes a nightmare.

## Vulnerability

JSONP opens up security holes. It assumes excessive trust and exposes CSRF (Cross-Site Request Forgery) vulnerabilities.

Bottom line: don't use JSONP.

# Alternatives

If not JSONP, then what? Check out the article "[What is CORS?](https://fullstack-developer.academy/what-is-cors/)" where several options are covered, including proxies and CORS packages.

For our example, we've got two ways to enable CORS on the API server:

```javascript
// api.js
const apiHandler = (request, response) => {
  response.header('Access-Control-Allow-Origin', '*');
  response.header('Access-Control-Request-Method', '*');
  response.header('Access-Control-Allow-Methods', 'OPTIONS, GET');
  response.header('Access-Control-Allow-Headers', '*');
  response.json({ message: 'Hello World!' });
};
```

> Please do not forget to remove the `dataType: 'jsonp'` property from the AJAX call.

> Please note that allowing access to all (`'*'`) is never a good idea. Be restrictive about who gets access.

Or install the `cors` package from npm: `npm i cors`. Once installed, you can add it globally (available to all routes) or as middleware on a single route:

```javascript
// api.js
const cors = require('cors');
app.use(cors());
// or
app.get('/api/hello', cors(), apiHandler);
```

# Conclusion

JSONP might look like a quick fix for CORS errors, but the downsides outweigh the benefits. The security vulnerabilities alone should steer you towards better options: proper CORS support on your API, or a proxy.
