Forms and Client-Server Interaction

Same-Origin Policy and Cross-Origin Resource Sharing


Learning Objectives

  • You know that browsers restrict programmatic cross-site interaction by default.
  • You know of same-origin policy and cross-origin resource sharing.

Missing ‘Access-Control-Allow-Origin’ Header

Browsers restrict programmatic cross-site interaction by default, but allow it if a server explicitly tells that cross-site interaction is allowed. The Same-Origin Policy (SOP) of browsers restrict how content from one origin can interact with another origin, while Cross-Origin Resource Sharing (CORS) policy from servers informs browsers whether resources from outside the site’s domain can be interacted with.

When a cross-origin request is blocked due to missing or invalid CORS headers, the failure is not directly visible in the page but appears as an error in the developer console. A sample CORS error is shown in Figure 1 below.

Fig 1 -- A typical browser console error "No 'Access-Control-Allow-Origin' header is present on the requested resource." when a cross-origin request is blocked by the CORS policy.

Fig 1 — A typical browser console error “No ‘Access-Control-Allow-Origin’ header is present on the requested resource.” when a cross-origin request is blocked by the CORS policy.

The error stems from the fact that the server did not include the Access-Control-Allow-Origin header in its response. This header is required for browsers to allow JavaScript to access the response of a cross-origin request.

You can try this with the following component, which queries an open API for random startup ideas. If you open up the address https://itsthisforthat.com/api.php?json directly in the browser, you’ll see that it works fine. However, if you try to fetch the same URL from a Svelte component, you’ll see the CORS error in the console.

<script>
  const API_URL = "https://itsthisforthat.com/api.php?json";
  let idea = $state("");

  const fetchIdea = async () => {
    const res = await fetch(API_URL);
    const data = await res.json();
    idea = data?.this + " for " + data?.that;
  };
</script>

<button onclick={fetchIdea}>Fetch idea</button>

<br/>

<p>Idea: {idea}</p>
Loading Exercise...

Same-Origin Policy

To prevent unauthorized access and use of resources from different sites, browsers enforce the Same-Origin Policy. It restricts how a document or script loaded from one origin can interact with resources from another origin.

The term origin refers to the combination of protocol (scheme), domain (host), and port of a URL. For example, the origin of https://example.com:8080/path is https://example.com:8080. Even https://example.com and https://example.com:8080 are considered different origins because they use different ports.

Under SOP, if JavaScript running on a page tries to fetch() a resource from a different origin, the browser will by default block the calling script from accessing the response. Some types of resources (images, stylesheets, <script> tags) are exempt from these restrictions, but programmatic access via JavaScript (e.g. fetch) is protected by SOP.

Web servers can explicitly allow cross-origin access by including CORS-specific headers in their responses, such as Access-Control-Allow-Origin. If such headers are missing or restrictive, the browser blocks the page’s JavaScript from using the response, and logs an error.

Figure 2 shows this process: the browser retrieves a page from one origin, then — following JavaScript instructions — requests JSON data from a different origin.

Fig 2 — A browser loading a site from one origin, then requesting JSON data from another origin.

Loading Exercise...

Cross-Origin Resource Sharing (CORS)

Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to tell browsers which cross-origin requests are permitted. Without CORS, the Same-Origin Policy prevents JavaScript on one origin from reading responses from another origin.

CORS headers

The server signals its CORS policy by including specific HTTP response headers. Browsers use these headers to decide whether JavaScript should be allowed to read the response.

The most common headers are:

  • Access-Control-Allow-Origin: Defines which origins are allowed. A value of * means “any origin is allowed” — but this cannot be used if credentials are included. To allow credentials, the value must be a specific origin, e.g. https://example.com.
  • Access-Control-Allow-Methods: Lists allowed HTTP methods, e.g. GET, POST, PUT.
  • Access-Control-Allow-Headers: Lists non-simple request headers that the client is allowed to send, e.g. Content-Type, Authorization.
  • Access-Control-Allow-Credentials: Indicates whether the browser may include credentials (cookies, HTTP authentication, or client TLS certificates).

Other useful headers include Access-Control-Expose-Headers (to make certain response headers readable in JS) and Access-Control-Max-Age (to cache preflight results).

Loading Exercise...

Simple and preflighted requests

CORS distinguishes between simple and preflighted requests.

A simple request meets all of these conditions:

  • Method is GET, HEAD, or POST
  • Only “simple” request headers are set (Accept, Accept-Language, Content-Language, etc.)
  • If there’s a request body, Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain

For simple requests, the browser sends the request directly. After receiving the response, the browser checks the CORS headers and either allows JavaScript to read the data or blocks it.

A preflighted request occurs when any of the above conditions are not met — for example:

  • Method is PUT, DELETE, or something else not listed above
  • A non-simple header (like Authorization) is sent
  • The Content-Type is application/json

In these cases, the browser automatically sends an HTTP OPTIONS request first. This preflight request includes:

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers (if applicable)

The server then replies with CORS headers indicating whether the actual request is allowed, where the response must include Access-Control-Allow-Origin and Access-Control-Allow-Methods (and possibly others). The browser then checks these headers, and if the request is permitted, it sends the actual request. Otherwise, JavaScript cannot access the response, and a CORS error is logged in the browser console.

CORS is enforced by browsers for security. Tools like curl or Postman will not block cross-origin requests — they will show the raw response regardless of CORS headers.

Loading Exercise...

Allowing Cross-Origin Requests in Hono

Hono comes with a CORS Middleware, which allows easy configuration of cross-origin requests. The simplest way to use the CORS middleware is to allow all origins by using the cors() function without any parameters. This will add the necessary headers to the response, allowing cross-origin requests from any origin.

import { Hono } from "@hono/hono";
// importing cors middleware
import { cors } from "@hono/hono/cors";

const app = new Hono();

// using the CORS middleware to allow all origins
app.use('/*', cors());

// rest of the application code, e.g.
app.get('/count', (c) => {
  return c.json({ count: 42 });
});

export default app;

Now, when we ask for the options from the server, we see a header access-control-allow-origin that defines the cross-origin policy. In this case, requests from browsers accessing sites at any origin are allowed to make requests to the server.

$ curl -X OPTIONS -v localhost:8000/count
...
< access-control-allow-methods: GET,HEAD,PUT,POST,DELETE,PATCH
< access-control-allow-origin: *
Loading Exercise...

Summary

In summary:

  • Browsers enforce the Same-Origin Policy (SOP) to restrict how scripts from one origin can interact with resources from another origin.
  • CORS is a mechanism that allows servers to specify which cross-origin requests are permitted by including specific HTTP headers in their responses.
  • Remember to check the browser console for CORS-related errors when debugging cross-origin request issues.
  • Simple requests meet certain criteria and are sent directly, while preflighted requests require an initial OPTIONS request to check permissions.
  • Hono provides built-in CORS middleware to easily configure cross-origin request policies for your server.
Loading Exercise...