# Origin Header

> Learn how the Origin header identifies where cross-origin requests come from. Essential for CORS security policies and preventing cross-site request forgery.

Source: https://howhttpworks.com/headers/origin
Last reviewed: 2026-10-05

> **TL;DR:** `Origin` identifies the initiating origin as scheme, host, and optional port, with no path or trailing slash. Browsers set it for CORS requests and many non-GET/HEAD requests, but not every cross-origin load. `Origin: null` is not a trusted website identity.

## What is Origin?

The header describes the origin that initiated a request, rather than the server receiving it. `Origin: https://app.example.com` and `Host: api.example.com` refer to different sides of a cross-origin API call. [Fetch defines current browser Origin behavior](https://fetch.spec.whatwg.org/#origin-header); RFC 6454 describes the origin concept.

## How Origin Works

Illustrative request header excerpt:

```http
GET /api/profile HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
```

If the API permits this origin, it can return `Access-Control-Allow-Origin: https://app.example.com`. If it does not, the browser prevents JavaScript from reading the response. That does not establish that the server never received or processed the request.

## Origin Format

```http
Origin: https://app.example.com
```

A non-default port is included, as in `http://localhost:5173`. A default HTTPS port is omitted from the serialized origin. Paths, queries, fragments, and user information are absent. An opaque origin is serialized as the literal `null`.

## When Browsers Send Origin

CORS requests carry Origin, including their preflights. Browsers also add it to requests with methods other than GET or HEAD, with rules that can produce `null`. A cross-origin image loaded without `crossorigin` normally uses `no-cors`; cross-origin GET/HEAD in that mode need not send Origin. [MDN documents the exceptions](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Origin#description).

Redirects can change the value to `null`; they do not universally remove the header. Referrer policy can also affect Origin on some non-CORS, non-GET/HEAD requests.

Compare these elements from a page on another origin:

```html
<img src="https://static.example.com/logo.png" alt="Plain image">
<img src="https://static.example.com/logo.png" crossorigin="anonymous" alt="CORS image">
```

The first is an ordinary no-cors image load. The second requests CORS and sends Origin; the image server must provide CORS permission. Adding `crossorigin` changes the loading contract, not just the headers you see in DevTools.

## Real-World Examples

A page at `https://app.example.com` fetching `https://api.example.com` is cross-origin even though both hosts can be same-site. A page at `http://localhost:5173` calling `http://localhost:3000` is also cross-origin because the ports differ.

Illustrative local-development JSON request excerpt:

```http
POST /api/orders HTTP/1.1
Host: localhost:3000
Origin: http://localhost:5173
Content-Type: application/json
```

Allow `http://localhost:5173` explicitly when that is the frontend's address. `http://127.0.0.1:5173` is another origin; replacing the host in the address bar can explain why the same code suddenly fails CORS. Neither value is the backend's `Host: localhost:3000`.

## CORS and Origin Validation

For an existing Express app, use an explicit allowlist with the `cors` package:

```javascript
const cors = require('cors')
app.use('/api', cors({
  origin: ['https://app.example.com', 'http://localhost:5173'],
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type']
}))
```

This is an example development allowlist; remove the localhost entry for a production-only deployment. The [middleware varies reflected responses by Origin](https://expressjs.com/en/resources/middleware/cors/). Avoid setting the response origin to an arbitrary caller-supplied string.

## Security Implications

Origin is a forbidden request header for browser scripts. Curl and other non-browser clients can set it freely. Treat it as a browser security signal, never API authentication or evidence that a user is an administrator.

## Common Patterns

When implementing CSRF checks, compare exact parsed origins rather than substring matches. `https://example.com.attacker.example` contains the string `example.com` but is not its origin. A missing Origin requires a deliberate route policy; do not turn it into an automatic authorization grant.

This middleware imposes a fail-closed origin check on a cookie-authenticated write route in an existing Express app:

```javascript
const trusted = new Set(['https://app.example.com'])
app.use('/api/settings', (req, res, next) => {
  if (req.method !== 'POST') return next()
  const origin = req.get('Origin')
  if (!trusted.has(origin)) return res.status(403).end()
  next()
})
```

Place it before the POST handler and retain authentication and CSRF-token validation. This route intentionally rejects missing Origin as well as `null`; if non-browser clients need access, design their authentication path explicitly. A curl caller can supply an allowed Origin and therefore still needs independent authentication. [OWASP describes origin checks and missing-header handling](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#checking-the-origin-header).

## Testing Origin Headers

```bash
curl -i https://api.example.com/api/profile \
  -H 'Origin: https://app.example.com'
```

Repeat with a disallowed origin and inspect the response. Then use browser Network to inspect the automatically generated Origin on the actual request. Curl demonstrates the server's response policy, not browser enforcement.

## Getting Origin right

Origin uses no trailing slash, whereas an origin-only Referer is a URL ending in `/`. For dynamically reflected CORS responses, preserve [Vary: Origin](https://howhttpworks.com/headers/vary) so caches distinguish responses selected by request origin.

When setting CORS headers manually, Express's `res.vary()` preserves other Vary fields:

```javascript
const allowed = new Set(['https://app.example.com', 'https://admin.example.com'])
app.use('/api', (req, res, next) => {
  res.vary('Origin')
  const origin = req.get('Origin')
  if (allowed.has(origin)) res.set('Access-Control-Allow-Origin', origin)
  next()
})
```

This fragment selects the allowed-origin header only; add full preflight and credentials handling if required. Vary also belongs on responses for disallowed origins so a cache knows origin influenced the selection. [Express documents res.vary](https://expressjs.com/en/5x/api/response/#res.vary).

## Common Issues

A missing Origin on a normal cross-origin image load is not necessarily a browser defect. A missing CORS response header on a fetch is a different problem. Check the request mode and resource type before changing the allowlist.

## The null Origin and Security Implications

A sandboxed iframe without `allow-same-origin` can initiate requests with `Origin: null`. Different unrelated opaque origins serialize identically, so allowing `null` does not identify a particular local file or trusted embed. [MDN warns against allowing null for CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Origin#directives).

For a controlled test, serve this iframe from an ordinary page:

```html
<iframe sandbox="allow-scripts" title="Opaque-origin test"
  srcdoc="<script>fetch('https://api.example.com/api/status').catch(console.error)</script>">
</iframe>
```

Inspect the API request in Network: the sandbox omits `allow-same-origin`, so the script runs with an opaque origin. Allowing `null` would grant the same serialized identity to other unrelated sandboxed documents. A response readable by this test frame is not proof that only this particular embed can access it.

## Related Headers

- [Referer](https://howhttpworks.com/headers/referer)
- [Access-Control-Allow-Origin](https://howhttpworks.com/headers/access-control-allow-origin)
- [Sec-Fetch-Site](https://howhttpworks.com/headers/sec-fetch-site)
- [Host](https://howhttpworks.com/headers/host)
