HTTP

Header

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.

5 min read beginner Try in Playground

What is Origin?

TL;DR: Identifies where a request originated from (scheme, host, port). Browsers send it automatically for CORS security and cross-origin request validation.

The Origin header identifies where a request is coming from. It’s like showing your passport at a border—it tells the server which website or domain is making the request. This is crucial for CORS (Cross-Origin Resource Sharing) security.

Browsers automatically add this header for cross-origin requests to help servers decide whether to allow or block the request.

How Origin Works

Cross-origin request from browser:

POST /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.mysite.com
Content-Type: application/json

{"message": "Hello"}
```text

**Server checks origin and responds:**

```http
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.mysite.com
Content-Type: application/json

{"status": "success"}

Origin Format

Complete Origin

Origin: https://example.com
Origin: https://app.example.com:8080
Origin: http://localhost:3000
```text

### Null Origin

```http
Origin: null

Used for file:// URLs, sandboxed iframes, or privacy-sensitive contexts.

When Browsers Send Origin

Always Sent For:

  • POST, PUT, PATCH, DELETE requests
  • Requests with custom headers
  • Requests from different origins

Examples:

// Cross-origin POST - Origin header added automatically
fetch('https://api.example.com/data', {
  method: 'POST',
  body: JSON.stringify({ data: 'test' })
})

// Same-origin GET - No Origin header
fetch('/api/local-data')

// Cross-origin GET with custom header - Origin header added
fetch('https://api.example.com/data', {
  headers: { 'X-Custom': 'value' }
})
```text

## Real-World Examples

### API Request from Web App

```http
POST /api/users HTTP/1.1
Host: api.company.com
Origin: https://app.company.com
Content-Type: application/json
Authorization: Bearer token123

{"name": "John Doe", "email": "john@example.com"}

CORS Preflight Request

OPTIONS /api/upload HTTP/1.1
Host: files.example.com
Origin: https://editor.myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
```text

### Form Submission

```http
POST /contact HTTP/1.1
Host: website.com
Origin: https://mysite.com
Content-Type: application/x-www-form-urlencoded

name=John&email=john%40example.com&message=Hello

WebSocket Connection

GET /websocket HTTP/1.1
Host: ws.example.com
Origin: https://chat.myapp.com
Upgrade: websocket
Connection: Upgrade
```javascript

## CORS and Origin Validation

### Server-Side Origin Checking

```javascript
app.use((req, res, next) => {
  const origin = req.headers.origin
  const allowedOrigins = ['https://myapp.com', 'https://www.myapp.com', 'https://admin.myapp.com']

  if (origin && allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin)
    res.header('Access-Control-Allow-Credentials', 'true')
  }

  next()
})

Dynamic Origin Validation

function isAllowedOrigin(origin) {
  // Allow subdomains of myapp.com
  if (origin && origin.match(/^https:\/\/[\w-]+\.myapp\.com$/)) {
    return true
  }

  // Allow localhost for development
  if (origin && origin.match(/^http:\/\/localhost:\d+$/)) {
    return true
  }

  return false
}

app.use((req, res, next) => {
  const origin = req.headers.origin

  if (isAllowedOrigin(origin)) {
    res.header('Access-Control-Allow-Origin', origin)
  }

  next()
})
```javascript

## Security Implications

### Origin Spoofing Protection

```javascript
// ❌ Vulnerable: Trusting Origin header blindly
app.post('/api/sensitive', (req, res) => {
  const origin = req.headers.origin
  if (origin === 'https://trusted.com') {
    // Dangerous! Origin can be spoofed in non-browser requests
    processRequest(req.body)
  }
})

// ✅ Secure: Use additional validation
app.post('/api/sensitive', (req, res) => {
  const origin = req.headers.origin
  const referer = req.headers.referer
  const userAgent = req.headers['user-agent']

  // Combine multiple checks
  if (isValidOrigin(origin) && isValidReferer(referer) && isValidUserAgent(userAgent)) {
    processRequest(req.body)
  }
})

CSRF Protection

app.use('/api', (req, res, next) => {
  if (req.method !== 'GET') {
    const origin = req.headers.origin
    const host = req.headers.host

    // Ensure origin matches host for same-origin requests
    if (origin && !origin.includes(host)) {
      const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || []

      if (!allowedOrigins.includes(origin)) {
        return res.status(403).json({ error: 'Forbidden origin' })
      }
    }
  }

  next()
})
```javascript

## Common Patterns

### Development vs Production Origins

```javascript
const getAllowedOrigins = () => {
  if (process.env.NODE_ENV === 'development') {
    return ['http://localhost:3000', 'http://localhost:5173', 'http://127.0.0.1:3000']
  }

  return ['https://myapp.com', 'https://www.myapp.com', 'https://admin.myapp.com']
}

Subdomain Wildcards

function isSubdomainAllowed(origin, baseDomain) {
  if (!origin) return false

  try {
    const url = new URL(origin)
    return url.hostname === baseDomain || url.hostname.endsWith(`.${baseDomain}`)
  } catch {
    return false
  }
}

// Usage
if (isSubdomainAllowed(origin, 'myapp.com')) {
  res.header('Access-Control-Allow-Origin', origin)
}
```javascript

### Mobile App Origins

```javascript
const allowedOrigins = [
  'https://myapp.com',
  'capacitor://localhost', // Capacitor apps
  'ionic://localhost', // Ionic apps
  'file://', // Cordova apps
  null // Some mobile contexts
]

Testing Origin Headers

Using curl

# Simulate cross-origin request
curl -H "Origin: https://myapp.com" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"test": "data"}' \
     https://api.example.com/data

# Test CORS preflight
curl -H "Origin: https://myapp.com" \
     -H "Access-Control-Request-Method: POST" \
     -X OPTIONS \
     https://api.example.com/data
```javascript

### Browser DevTools

1. Open Network tab
2. Make cross-origin request
3. Look for Origin header in request
4. Check Access-Control-Allow-Origin in response

### JavaScript Testing

```javascript
// This will include Origin header automatically
fetch('https://api.different-domain.com/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ test: 'data' })
}).then((response) => {
  if (!response.ok) {
    console.log('CORS might be blocking this request')
  }
  return response.json()
})

Best Practices

Validate Origins Strictly

// ✅ Whitelist specific origins
const allowedOrigins = ['https://myapp.com', 'https://www.myapp.com']

// ❌ Don't use wildcards in production
res.header('Access-Control-Allow-Origin', '*')
```javascript

### Handle Null Origins

```javascript
app.use((req, res, next) => {
  const origin = req.headers.origin

  // Handle null origin (file://, sandboxed iframes)
  if (origin === 'null' && process.env.ALLOW_NULL_ORIGIN === 'true') {
    res.header('Access-Control-Allow-Origin', 'null')
  } else if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin)
  }

  next()
})

Log Suspicious Origins

app.use((req, res, next) => {
  const origin = req.headers.origin

  if (origin && !isAllowedOrigin(origin)) {
    console.warn(`Blocked request from suspicious origin: ${origin}`, {
      ip: req.ip,
      userAgent: req.headers['user-agent'],
      path: req.path
    })
  }

  next()
})
```text

## Common Issues

### Missing CORS Headers

```javascript
// Problem: Origin sent but no CORS response
// Request: Origin: https://myapp.com
// Response: (no Access-Control-Allow-Origin)
// Result: Browser blocks the response

// Solution: Add proper CORS headers
res.header('Access-Control-Allow-Origin', 'https://myapp.com')

Wildcard with Credentials

// ❌ This doesn't work
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Credentials', 'true')

// ✅ Use specific origin with credentials
res.header('Access-Control-Allow-Origin', 'https://myapp.com')
res.header('Access-Control-Allow-Credentials', 'true')

The null Origin and Security Implications

Browsers send Origin: null in several situations: requests from file:// URLs, requests from sandboxed iframes without the allow-same-origin permission, redirects that cross origins, and some data URL contexts. The null origin is a special case that requires careful handling on the server.

Never add null to your CORS allowlist with Access-Control-Allow-Origin: null. An attacker can craft a sandboxed iframe that sends requests with Origin: null, and if your server allows it, the attacker gains cross-origin access to your API. The null origin is not a trustworthy identifier because it can be generated by any page using a sandboxed iframe.

The practical difference between Origin and Referer is that Origin contains only the scheme, host, and port (no path), while Referer includes the full URL path. This makes Origin more privacy-preserving for cross-origin requests, since it does not reveal the specific page the user was on. The Referrer-Policy header controls Referer behavior, but Origin is always sent for cross-origin requests regardless of referrer policy.

Frequently Asked Questions

What is the Origin header?

Origin identifies where a request originated from (scheme, host, port). Browsers send it automatically with cross-origin requests and POST requests for CORS and CSRF protection.

When is Origin sent?

Browsers send Origin with all cross-origin requests, same-origin POST/PUT/DELETE, and requests triggered by certain APIs. It is not sent with same-origin GET or following redirects.

What is the difference between Origin and Referer?

Origin contains only scheme://host:port without path. Referer includes the full URL path. Origin is more privacy-preserving and specifically designed for CORS.

Can Origin be spoofed?

Browsers prevent JavaScript from modifying Origin. However, non-browser clients can send any Origin. Server-side CORS is for browser security, not API authentication.

Keep Learning