HTTP

Header

Proxy-Authenticate Header

Learn how the Proxy-Authenticate header challenges clients for credentials when accessing resources through a proxy. Understand proxy authentication schemes.

6 min read advanced Try in Playground

What is Proxy-Authenticate?

TL;DR: Challenges clients for authentication credentials when accessing resources through a proxy server. Sent with 407 status code when proxy requires login.

The Proxy-Authenticate header is sent by a proxy server to challenge the client for authentication credentials. It’s like a security checkpoint saying “You need to prove who you are before I’ll let you through to the destination server.”

This is similar to WWW-Authenticate but specifically for proxy server authentication rather than origin server authentication.

How Proxy-Authenticate Works

Client makes request through proxy:

GET https://api.example.com/data HTTP/1.1
Host: api.example.com
```text

**Proxy challenges for credentials:**

```http
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="Corporate Proxy"

Proxy authentication required

Client sends credentials:

GET https://api.example.com/data HTTP/1.1
Host: api.example.com
Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==
```text

**Proxy forwards request:**

```http
HTTP/1.1 200 OK
Content-Type: application/json

{"data": "success"}

Syntax

Proxy-Authenticate: <auth-scheme> realm="<realm>"
Proxy-Authenticate: <auth-scheme> realm="<realm>", <directives>
```text

### Common Auth Schemes

- **Basic** - Username and password (base64 encoded)
- **Digest** - More secure than Basic
- **Bearer** - Token-based authentication
- **Negotiate** - SPNEGO/Kerberos authentication

## Common Examples

### Basic Authentication

```http
Proxy-Authenticate: Basic realm="Corporate Network"

Simple username/password authentication.

Digest Authentication

Proxy-Authenticate: Digest realm="proxy", qop="auth",
  nonce="abc123xyz", opaque="def456"
```text

More secure challenge-response authentication.

### Multiple Options

```http
Proxy-Authenticate: Negotiate
Proxy-Authenticate: Basic realm="Proxy Server"

Offer multiple authentication methods.

Bearer Token

Proxy-Authenticate: Bearer realm="proxy", error="invalid_token"
```text

OAuth 2.0 style token authentication.

## Real-World Scenarios

### Corporate Proxy

```http
# Employee tries to access internet
GET https://news.example.com HTTP/1.1

# Corporate proxy requires authentication
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="CorpProxy"
Proxy-Authenticate: Negotiate

# Client sends Windows credentials
GET https://news.example.com HTTP/1.1
Proxy-Authorization: Negotiate YIIBvwYJKoZIhvcSAQICAQBu...

# Proxy authenticates and forwards
HTTP/1.1 200 OK

Digest Proxy Authentication

# Initial request
GET https://api.example.com/data HTTP/1.1

# Proxy challenge
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Digest realm="proxy",
  qop="auth",
  nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
  opaque="5ccc069c403ebaf9f0171e9517f40e41"

# Client response
GET https://api.example.com/data HTTP/1.1
Proxy-Authorization: Digest username="user",
  realm="proxy",
  nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
  uri="https://api.example.com/data",
  response="6629fae49393a05397450978507c4ef1",
  opaque="5ccc069c403ebaf9f0171e9517f40e41"

# Success
HTTP/1.1 200 OK
```text

### Failed Authentication

```http
# Invalid credentials
GET https://api.example.com HTTP/1.1
Proxy-Authorization: Basic aW52YWxpZDpjcmVkcw==

# Proxy rejects
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="Proxy", error="invalid_credentials"

Invalid proxy credentials

Server (Proxy) Implementation

Express Proxy with Authentication

const express = require('express')
const httpProxy = require('http-proxy')
const app = express()

const proxy = httpProxy.createProxyServer({})

// Simple credential store
const validCredentials = {
  user1: 'password1',
  user2: 'password2'
}

// Check Basic auth
function checkProxyAuth(req, res, next) {
  const proxyAuth = req.headers['proxy-authorization']

  if (!proxyAuth) {
    res.setHeader('Proxy-Authenticate', 'Basic realm="Corporate Proxy"')
    return res.status(407).send('Proxy Authentication Required')
  }

  // Parse Basic auth
  const authHeader = proxyAuth.replace('Basic ', '')
  const decoded = Buffer.from(authHeader, 'base64').toString()
  const [username, password] = decoded.split(':')

  // Validate credentials
  if (validCredentials[username] !== password) {
    res.setHeader('Proxy-Authenticate', 'Basic realm="Corporate Proxy"')
    return res.status(407).send('Invalid proxy credentials')
  }

  // Store authenticated user
  req.proxyUser = username
  next()
}

// Apply auth to all requests
app.use(checkProxyAuth)

// Proxy requests
app.use('/', (req, res) => {
  console.log(`Proxying request for user: ${req.proxyUser}`)

  proxy.web(req, res, {
    target: `${req.protocol}://${req.get('host')}`,
    changeOrigin: true
  })
})

app.listen(3128, () => {
  console.log('Proxy server listening on port 3128')
})
```javascript

### Digest Authentication Proxy

```javascript
const crypto = require('crypto')

function generateNonce() {
  return crypto.randomBytes(16).toString('hex')
}

function digestProxyAuth(req, res, next) {
  const proxyAuth = req.headers['proxy-authorization']

  if (!proxyAuth) {
    const nonce = generateNonce()
    const opaque = crypto.randomBytes(16).toString('hex')

    res.setHeader(
      'Proxy-Authenticate',
      `Digest realm="proxy", qop="auth", nonce="${nonce}", opaque="${opaque}"`
    )
    return res.status(407).send('Proxy Authentication Required')
  }

  // Parse Digest auth
  const params = {}
  proxyAuth
    .replace('Digest ', '')
    .split(',')
    .forEach((part) => {
      const [key, value] = part.trim().split('=')
      params[key] = value.replace(/"/g, '')
    })

  // Validate digest
  const username = params.username
  const password = validCredentials[username]

  if (!password) {
    res.setHeader('Proxy-Authenticate', 'Digest realm="proxy"')
    return res.status(407).send('Invalid credentials')
  }

  // Calculate expected response
  const ha1 = crypto.createHash('md5').update(`${username}:proxy:${password}`).digest('hex')

  const ha2 = crypto.createHash('md5').update(`${req.method}:${params.uri}`).digest('hex')

  const expected = crypto.createHash('md5').update(`${ha1}:${params.nonce}:${ha2}`).digest('hex')

  if (params.response !== expected) {
    res.setHeader('Proxy-Authenticate', 'Digest realm="proxy"')
    return res.status(407).send('Invalid digest')
  }

  req.proxyUser = username
  next()
}

Python Proxy with Authentication

from http.server import HTTPServer, BaseHTTPRequestHandler
import base64

VALID_CREDENTIALS = {
    'user1': 'password1',
    'user2': 'password2'
}

class ProxyHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # Check Proxy-Authorization header
        proxy_auth = self.headers.get('Proxy-Authorization')

        if not proxy_auth:
            self.send_response(407)
            self.send_header('Proxy-Authenticate', 'Basic realm="Proxy"')
            self.end_headers()
            self.wfile.write(b'Proxy Authentication Required')
            return

        # Parse Basic auth
        try:
            auth_type, credentials = proxy_auth.split(' ', 1)
            if auth_type.lower() != 'basic':
                raise ValueError('Unsupported auth type')

            decoded = base64.b64decode(credentials).decode('utf-8')
            username, password = decoded.split(':', 1)

            # Validate
            if VALID_CREDENTIALS.get(username) != password:
                self.send_response(407)
                self.send_header('Proxy-Authenticate', 'Basic realm="Proxy"')
                self.end_headers()
                self.wfile.write(b'Invalid credentials')
                return

            # Forward request to destination
            # ... proxy logic here ...

            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'Success')

        except Exception as e:
            self.send_response(407)
            self.send_header('Proxy-Authenticate', 'Basic realm="Proxy"')
            self.end_headers()
            self.wfile.write(b'Authentication failed')

server = HTTPServer(('', 3128), ProxyHandler)
server.serve_forever()
```text

## Best Practices

### For Proxy Servers

**1. Always use 407 status code**

```http
# ✅ Correct proxy auth challenge
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="Proxy"

2. Prefer secure authentication methods

# ✅ Offer secure options first
Proxy-Authenticate: Digest realm="proxy", qop="auth", nonce="..."
Proxy-Authenticate: Basic realm="proxy"

# ❌ Only Basic is less secure
Proxy-Authenticate: Basic realm="proxy"
```javascript

**3. Use HTTPS for proxy connections**

```javascript
// ✅ Secure proxy (HTTPS)
const proxy = {
  host: 'proxy.example.com',
  port: 443,
  protocol: 'https'
}

// ❌ Unencrypted Basic auth is vulnerable

4. Implement rate limiting

const attempts = new Map()

function checkAuthAttempts(clientIp) {
  const count = attempts.get(clientIp) || 0

  if (count > 5) {
    return res.status(429).send('Too many authentication attempts')
  }

  attempts.set(clientIp, count + 1)
}
```javascript

**5. Log authentication attempts**

```javascript
function logProxyAuth(username, success, clientIp) {
  console.log({
    timestamp: new Date(),
    user: username,
    success,
    ip: clientIp
  })
}

For Clients

1. Store proxy credentials securely

// ✅ Use secure credential storage
const credentials = await getSecureCredentials('proxy')

// ❌ Don't hardcode
const credentials = { user: 'user', pass: 'password' }
```javascript

**2. Handle 407 responses**

```javascript
const response = await fetch(url, {
  proxy: proxyUrl
})

if (response.status === 407) {
  // Prompt for proxy credentials
  const credentials = await promptForProxyCredentials()

  // Retry with credentials
  return fetch(url, {
    proxy: proxyUrl,
    headers: {
      'Proxy-Authorization': `Basic ${btoa(credentials)}`
    }
  })
}

3. Reuse proxy authentication

// ✅ Cache proxy auth for session
const proxyAuth = `Basic ${btoa(`${username}:${password}`)}`

fetch(url, {
  headers: {
    'Proxy-Authorization': proxyAuth
  }
})
```text

## Proxy-Authenticate vs WWW-Authenticate

### Proxy-Authenticate (Proxy Layer)

```http
# For proxy server authentication
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="Proxy"

# Client responds with
Proxy-Authorization: Basic credentials

WWW-Authenticate (Origin Server)

# For origin server authentication
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="API"

# Client responds with
Authorization: Basic credentials
```text

### Both Can Occur Together

```http
# Proxy auth required first
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="Proxy"

# After proxy auth, origin might also require auth
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="API"

# Client sends both
Proxy-Authorization: Basic proxy-credentials
Authorization: Basic api-credentials

Common Proxy Authentication Schemes

Basic

Proxy-Authenticate: Basic realm="Corporate Proxy"
```text

Simple but should only be used over HTTPS.

### Digest

```http
Proxy-Authenticate: Digest realm="proxy", qop="auth", nonce="..."

More secure than Basic, challenge-response mechanism.

Negotiate (SPNEGO)

Proxy-Authenticate: Negotiate
```text

Kerberos/NTLM for Windows environments.

### Bearer

```http
Proxy-Authenticate: Bearer realm="proxy"

Token-based authentication (OAuth 2.0 style).

Testing Proxy-Authenticate

Using curl

# Test proxy without credentials (should get 407)
curl -x http://proxy.example.com:3128 https://api.example.com

# Test with proxy credentials
curl -x http://proxy.example.com:3128 \
  --proxy-user username:password \
  https://api.example.com

# View proxy auth challenge
curl -x http://proxy.example.com:3128 -v https://api.example.com 2>&1 | \
  grep -i "proxy-authenticate"
```javascript

### Using Node.js

```javascript
const https = require('https')

const options = {
  host: 'api.example.com',
  path: '/data',
  headers: {
    'Proxy-Authorization': 'Basic ' + Buffer.from('username:password').toString('base64')
  },
  proxy: {
    host: 'proxy.example.com',
    port: 3128
  }
}

https.get(options, (res) => {
  if (res.statusCode === 407) {
    console.log('Proxy auth required:', res.headers['proxy-authenticate'])
  } else {
    console.log('Success:', res.statusCode)
  }
})

Using fetch with proxy

// Configure proxy (Node.js with proxy agent)
const ProxyAgent = require('proxy-agent')

const proxyAuth = Buffer.from('username:password').toString('base64')

const agent = new ProxyAgent({
  protocol: 'http',
  host: 'proxy.example.com',
  port: 3128,
  headers: {
    'Proxy-Authorization': `Basic ${proxyAuth}`
  }
})

fetch('https://api.example.com/data', { agent })
  .then((res) => console.log('Response:', res.status))
  .catch((err) => console.error('Error:', err))

Frequently Asked Questions

What is Proxy-Authenticate?

Proxy-Authenticate is sent with 407 responses when a proxy requires authentication. It works like WWW-Authenticate but for proxy servers instead of origin servers.

When is Proxy-Authenticate used?

Corporate proxies often require authentication to access the internet. The proxy sends 407 with Proxy-Authenticate, and clients respond with Proxy-Authorization.

What is the difference between 401 and 407?

401 means the origin server requires authentication (use WWW-Authenticate). 407 means a proxy requires authentication (use Proxy-Authenticate).

How do I authenticate with a proxy?

After receiving 407 with Proxy-Authenticate, send the request again with Proxy-Authorization header containing credentials in the specified scheme.

Keep Learning