# WWW-Authenticate

> Learn how the WWW-Authenticate header specifies authentication methods required to access protected resources. Understand Basic, Bearer, and Digest schemes.

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

> **TL;DR:** `WWW-Authenticate` is the response header that tells a client how to log in, for example `WWW-Authenticate: Basic realm="Admin"` or `Bearer realm="API"`. Every `401 Unauthorized` must include at least one challenge; the client answers with an `Authorization` header in that scheme. For Bearer: no token gets a bare challenge, a bad or expired token gets 401 with `error="invalid_token"`, and a token lacking scope gets 403 with `error="insufficient_scope"`.

The header describes how to authenticate for the requested resource. It's mandatory on a 401, and servers may send challenges on other responses too. [RFC 9110 §11.6.1](https://www.rfc-editor.org/rfc/rfc9110#section-11.6.1) sets the rule.

## How It Works

Here's a server challenging a request that arrived without credentials (examples on this page are trimmed):

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Admin", charset="UTF-8"
Content-Length: 0
```

The client can retry with a matching `Authorization` header. Whether a browser shows a login prompt or retries on its own depends on the client and the kind of request.

## Authentication Schemes

**Basic** sends `username:password` base64-encoded. Base64 is an encoding, not encryption, so only use Basic over TLS. **Bearer** grants access to whoever holds the token; the token can be any string and needn't be a JWT. **Digest** has the client compute a response from the server's nonce and other parameters. Its security depends on the algorithm, so check which one you're using.

See [RFC 7617](https://www.rfc-editor.org/rfc/rfc7617), [RFC 6750](https://www.rfc-editor.org/rfc/rfc6750), and [RFC 7616](https://www.rfc-editor.org/rfc/rfc7616) for these schemes. RFC 6750 defines OAuth 2.0 Bearer authentication.

A realm names a protection space: a set of resources that share credentials. Basic challenges must include one; for Bearer it's optional. It's only a label, with no permissions or login URL attached. [RFC 9110 §11.5](https://www.rfc-editor.org/rfc/rfc9110#section-11.5) defines protection spaces.

## Multiple Authentication Options

A response can offer several challenges. This one sends two on separate header lines:

```http
WWW-Authenticate: Basic realm="Admin"
WWW-Authenticate: Bearer realm="API"
```

Clients pick a scheme they support, and nothing says it has to be the first one listed. If you parse this header yourself, be careful with commas: they separate both parameters and challenges, and can appear inside quoted values. A plain `split(',')` gets all of that wrong.

## OAuth 2.0 Error Parameters

[RFC 6750 §3.1](https://www.rfc-editor.org/rfc/rfc6750#section-3.1) defines three Bearer error codes and the status that normally goes with each:

- `invalid_request`: malformed authentication request; normally 400.
- `invalid_token`: expired, revoked, or otherwise invalid token; normally 401.
- `insufficient_scope`: valid authentication lacking required scope; normally 403, with `scope` optionally naming what is needed.

When the request carried no credentials at all, leave out the error code and error details. A bare `WWW-Authenticate: Bearer realm="API"` is all the client needs.

## Real-World Examples

The response to an expired token:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="API", error="invalid_token"
Content-Length: 0
```

The response to a valid token without the needed permission:

```http
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer realm="API", error="insufficient_scope", scope="reports:read"
Content-Length: 0
```

`reports:read` is a made-up application scope; scope names are whatever your API defines.

## Server Implementation

nginx sends a Basic challenge through its [auth_basic module](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html). First create the password file with Apache's `htpasswd` utility:

```bash
htpasswd -c /etc/nginx/admin.htpasswd alice
```

`-c` creates the file, wiping it if it exists, so leave it off when you add more users. Then add this to an existing TLS server block:

```nginx
location /admin/ {
    auth_basic "Admin";
    auth_basic_user_file /etc/nginx/admin.htpasswd;
    proxy_pass http://127.0.0.1:3000;
}
```

Check the challenge, then log in (curl prompts for the password):

```bash
curl -i https://example.com/admin/
curl --user alice -i https://example.com/admin/
```

Use your own hostname, and make sure the nginx worker can read the password file. [Apache documents htpasswd](https://httpd.apache.org/docs/2.4/programs/htpasswd.html) and the password formats it supports.

### Express Basic authentication

Let a middleware decode the credentials instead of writing your own `split(':')`. Install `express` and `express-basic-auth`, save this as `app.mjs`, and set a test password in `ADMIN_PASSWORD`:

```javascript
import express from 'express'
import basicAuth from 'express-basic-auth'

const password = process.env.ADMIN_PASSWORD
if (!password) throw new Error('Set ADMIN_PASSWORD')
const app = express()
app.use('/admin', basicAuth({
  users: { alice: password },
  challenge: true,
  realm: 'Admin'
}))
app.get('/admin', (req, res) => res.json({ status: 'authenticated' }))
app.listen(3000, '127.0.0.1')
```

The package's [challenge option](https://github.com/LionC/express-basic-auth#challenge) adds WWW-Authenticate to rejected requests; without it you get a 401 with no challenge. The in-memory account is a demo; a real deployment checks your user store and runs behind TLS. Test both paths:

```bash
curl -i http://127.0.0.1:3000/admin
curl --user alice -i http://127.0.0.1:3000/admin
```

The second command prompts for the password. Register the auth middleware before the protected route, so it runs first. A JSON body that says "unauthorized" isn't a challenge; the header is.

## Security Considerations

If you decode Basic credentials yourself, split at the first colon only: usernames can't contain a colon, but passwords can. Scheme names are case-insensitive, so `basic` is as valid as `Basic`. Parsers that match an exact `Basic ` prefix or split parameters on commas break on valid input; use a tested library.

## Getting WWW-Authenticate right

Look at the actual response on the wire, not just the status your app sets. Every 401 needs the challenge header, whatever the body says. Check that it survives your reverse proxy, too; RFC 9110 forbids a proxy from modifying WWW-Authenticate when it forwards a response.

## Related Headers

- [Authorization](https://howhttpworks.com/headers/authorization)
- [Proxy-Authenticate](https://howhttpworks.com/headers/proxy-authenticate)
- [Authentication-Info](https://howhttpworks.com/headers/authentication-info)
- [Set-Cookie](https://howhttpworks.com/headers/set-cookie)
