Mastering CORS: How to Fix Cross-Origin Errors in Web Apps
Learn what CORS (Cross-Origin Resource Sharing) is, why browsers block cross-origin requests, and how to configure servers—using Java, Express.js, or Spring Boot—to handle preflight checks, simple requests, and fine-grained security headers, ensuring safe and functional web applications.
When building a web application you may encounter an error like "blocked by CORS policy". This article explains what CORS is and how to handle it.
Access to fetch at 'https://bank.com/api/balances'
from origin 'http://real-bank.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.Let's break down CORS.
What Is CORS?
CORS stands for Cross-Origin Resource Sharing. It is a security feature implemented by web browsers to prevent malicious sites from accessing resources they are not authorized to.
By default browsers block cross‑origin requests unless the server explicitly allows them via CORS headers.
Origin is defined by the scheme (http or https), host name, and port. For example, https://example.com:443 is a different origin from http://example.com:80 or https://sub.example.com:443.
This matters because a malicious site like real-bank.com could try to call your bank's API ( bank.com/api/) to steal data, but CORS blocks the request before it reaches the server.
Simply putting resources on the same origin is not always possible. As your app grows you may need to interact with third‑party APIs or serve assets from different domains, requiring proper CORS configuration on the server.
How CORS Works
When the browser detects a cross‑origin request it first sends a preflight OPTIONS request containing the Origin header and other request metadata.
The server must respond with appropriate CORS headers, most importantly Access-Control-Allow-Origin, set to the requesting origin or * to allow any origin.
If the server does not include the required header or the origin does not match, the browser blocks the request.
Simple Request vs Preflight Request
Not every cross‑origin request triggers a preflight. A request is considered *simple* if it meets all of the following conditions:
Uses one of the methods GET, HEAD or POST.
Only includes "safe" headers such as Accept, Accept-Language, Content-Language, or Content-Type with values application/x-www-form-urlencoded, multipart/form-data or text/plain.
Does not use custom headers, credentials, or non‑standard content types.
Requests that do not meet these criteria are preflighted with an OPTIONS request.
Handling Preflight Requests
The decision to allow or reject the actual request is made by the server. The server must set at least the Access-Control-Allow-Origin header; otherwise the browser blocks the request.
For a permissive setup you can set the header to *:
responseHeaders.add("Access-Control-Allow-Origin", "*");Below is a simplified Java HTTP server that adds CORS headers:
public static void main(String[] args) throws IOException {
var server = HttpServer.create(new InetSocketAddress(3000), 0);
server.createContext("/", exchange -> {
var responseHeaders = exchange.getResponseHeaders();
responseHeaders.add("Access-Control-Allow-Origin", "*");
exchange.sendResponseHeaders(200, 0);
});
server.start();
}Fine‑Grained CORS
In production you should follow the principle of least privilege and specify exact origins instead of *. Use the Origin request header to dynamically set Access-Control-Allow-Origin only for allowed origins.
var allowedOrigins = List.of("https://bank.com");
var requestHeaders = exchange.getRequestHeaders();
var responseHeaders = exchange.getResponseHeaders();
var origin = requestHeaders.getFirst("Origin");
if (origin != null && allowedOrigins.contains(origin)) {
responseHeaders.add("Access-Control-Allow-Origin", origin);
}Other important CORS headers include:
Access-Control-Allow-Methods : Lists HTTP methods allowed (e.g., GET, POST, PUT).
Access-Control-Allow-Headers : Lists custom request headers that the client may send (e.g., X-Request-Id).
Access-Control-Expose-Headers : Lists response headers that the client is allowed to read (e.g., X-RateLimit-Remaining).
Access-Control-Max-Age : Caches the preflight response for a number of seconds.
Access-Control-Allow-Credentials : Indicates whether the browser may include credentials (cookies, HTTP authentication). When set to true, Access-Control-Allow-Origin cannot be *.
Example of a complete CORS configuration in Java:
var allowedOrigins = List.of("https://bank.com");
var requestHeaders = exchange.getRequestHeaders();
var responseHeaders = exchange.getResponseHeaders();
var origin = requestHeaders.getFirst("Origin");
if (origin != null && allowedOrigins.contains(origin)) {
responseHeaders.add("Access-Control-Allow-Origin", origin);
responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, PUT");
responseHeaders.add("Access-Control-Allow-Headers", "X-Request-Id");
responseHeaders.add("Access-Control-Expose-Headers", "X-RateLimit-Remaining");
responseHeaders.add("Access-Control-Max-Age", "3600");
responseHeaders.add("Access-Control-Allow-Credentials", "true");
}Frameworks often provide built‑in CORS support. For example, Express.js:
const cors = require('cors');
app.use(cors({
origin: 'https://bank.com',
methods: ['GET', 'POST', 'PUT'],
allowedHeaders: ['X-Request-Id'],
exposedHeaders: ['X-RateLimit-Remaining'],
maxAge: 3600,
credentials: true
}));Or Spring Boot using @CrossOrigin:
@RestController
@RequestMapping("/api")
@CrossOrigin(
origins = "https://bank.com",
allowedHeaders = "X-Request-Id",
exposedHeaders = "X-RateLimit-Remaining",
maxAge = 3600,
allowCredentials = true)
public class ApiController {
}Or a Spring configuration class:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://bank.com")
.allowedMethods("GET", "POST", "PUT")
.allowedHeaders("X-Request-Id")
.exposedHeaders("X-RateLimit-Remaining")
.maxAge(3600)
.allowCredentials(true);
}
}Conclusion
Remember: CORS is a browser‑enforced security feature. The server declares who may access resources, but only the browser enforces it.
CORS does not protect your API from attackers; it protects users in their browsers. Additional layers such as authentication, rate limiting, and proper authorization are still required.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
