Understanding Same-Origin Policy
Browsers enforce the Same-Origin Policy (SOP) as a fundamental security mechanism. SOP prevents JavaScript on one origin from interacting with resources on a different origin. An origin is defined by the combination of three elements: protocol, hostname, and port. If any of these differ between two URLs, the browser treats them as cross-origin and blocks restricted interactions.
Consider a base URL: http://127.0.0.1:8080/app/home
https://127.0.0.1:8080/app/home— Protocol mismatch (HTTPS vs HTTP)http://192.168.1.10:8080/app/home— Host mismatchhttp://127.0.0.1:8888/app/home— Port mismatchhttp://localhost:8080/app/home— Host mismatch (localhost vs 127.0.0.1)
When a web page loaded from http://127.0.0.1:8020 attempts an AJAX request to http://localhost:8080, the browser intercepts the response due to the mismatched host and port.
JSONP Mechanism
While SOP restricts AJAX calls, HTML tags with src attributes—such as <script>, <img>, and <iframe>—are exempt from this policy. JSONP exploits the <script> tag capability. The client dynamically creates a script element pointing to a remote endpoint. The server processes the request and returns the data wrapped inside a JavaScript function invocation. When the browser evaluates the fetched script, the designated callback function executes, making the data available to the client application.
Implementing JSONP via jQuery $.ajax
jQuery simplifies JSONP execution through the $.ajax method. By setting dataType: 'jsonp', jQuery bypasses SOP limitations. Adding the jsonp configuration property specifies the query string parameter name used to pass the callback function identifier to the server. jQuery automatically generates a callback function name, appends it to the request URL, and intercepts the server response, routing it directly into the success handler.
Frontend Implementation
<html>
<head>
<meta charset="UTF-8"/>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function validateUser() {
var userVal = $("#userInput").val();
if (!userVal) {
$("#feedbackMsg").text("Username cannot be empty");
return;
}
$("#feedbackMsg").text("");
$.ajax({
type: "GET",
url: "http://localhost:8080/app/validateUser",
data: { user: userVal },
dataType: "jsonp",
jsonp: "cbParam",
success: function(result) {
$("#feedbackMsg").text(result);
}
});
}
</script>
</head>
<body>
<form action="/submitForm">
Username: <input id="userInput" type="text" onblur="validateUser()">
<span id="feedbackMsg" style="color: red"></span><br/>
Password: <input type="password" name="pass"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>Backend Implementation (Java Servlet)
The server must read the callback parameter specified by the client's jsonp setting (cbParam in this instance) and wrap the response string inside that function call.
package com.example.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/validateUser")
public class UserValidationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String user = req.getParameter("user");
String handlerName = req.getParameter("cbParam");
String status;
if ("admin".equals(user)) {
status = "Username already taken";
} else {
status = "Username is available";
}
resp.setContentType("text/javascript;charset=UTF-8");
resp.getWriter().print(handlerName + "('" + status + "')");
}
}Implementing JSONP via jQuery $.getJSON
The $.getJSON utility also supports cross-origin requests. To trigger JSONP behavior, append a callback query parameter with a value of ? to the URL string (e.g., ?jsonpCallback=?). jQuery replaces the placeholder ? with a dynamically generated function name, removing the need for manual callback definitions.
Frontend Implementation
<html>
<head>
<meta charset="UTF-8"/>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
function validateUser() {
var userVal = $("#userInput").val();
if (!userVal) {
$("#feedbackMsg").text("Username cannot be empty");
return;
}
$("#feedbackMsg").text("");
$.getJSON(
"http://localhost:8080/app/validateUser?jsonpCallback=?",
{ user: userVal },
function(result) {
$("#feedbackMsg").text(result);
}
);
}
</script>
</head>
<body>
<form action="/submitForm">
Username: <input id="userInput" type="text" onblur="validateUser()">
<span id="feedbackMsg" style="color: red"></span><br/>
Password: <input type="password" name="pass"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>Backend Implementation (Java Servlet)
The server-side logic reads the callback parameter appended to the URL (jsonpCallback) to formulate the executable script response.
package com.example.servlet;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/validateUser")
public class UserValidationServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String user = req.getParameter("user");
String handlerName = req.getParameter("jsonpCallback");
String status;
if ("admin".equals(user)) {
status = "Username already taken";
} else {
status = "Username is available";
}
resp.setContentType("text/javascript;charset=UTF-8");
resp.getWriter().print(handlerName + "('" + status + "')");
}
}Alternative: Cross-Origin Resource Sharing (CORS)
Instead of using JSONP workarounds, modern applications often implement Cross-Origin Resource Sharing (CORS) directly on the server. By setting specific HTTP response headers within a filter or interceptor, the backend explicitly permits external domains to access its resources.
// Servlet or Filter response header configuration
resp.setHeader("Access-Control-Allow-Origin", "*"); // Permits any origin
resp.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, DELETE");
resp.setHeader("Access-Control-Max-Age", "3600");
resp.setHeader("Access-Control-Allow-Headers", "x-requested-with");