skip to content

An HTTP response returns 401 with more than one WWW-Authenticate challenge, for example both a Negotiate challenge and a Basic one. How should a client handle that, and what makes the challenge list hard to parse correctly?

level: seniorimportance: should knowfreq 26%

answer

  1. several challenges, pick strongest supported
  2. one Authorization header only
  3. token68 OR auth-params, never both
  4. commas separate challenges AND params
  5. IANA scheme registry keeps names unique

basics

~20 s

A server may offer several schemes; the client picks the strongest one it supports and ignores the rest, answering with a single Authorization header. Parsing is tricky because challenges are comma-separated and so are their auth-params, so a naive split on commas cannot tell where one challenge ends.

solid answer

~50 s

A 401 may carry **several challenges**, either as one comma-separated `WWW-Authenticate` value or as repeated header fields. The client must pick **one** scheme it supports — by convention the strongest it can honour, not the first listed — and send exactly one `Authorization` header. Unknown schemes are ignored; if none is supported the response is simply an error. The parsing hazard is the grammar. A challenge is `scheme` followed by either a `token68` blob **or** a comma-separated list of `name=value` params. Since challenges are *also* comma-separated, `Negotiate, Basic realm="a", charset="UTF-8"` cannot be split on commas: a parser must recognise that a bare token starting a new element is a new scheme, and that `charset="UTF-8"` belongs to `Basic`. Quoted strings may themselves contain commas. Scheme and parameter names are case-insensitive; parameter values in quotes are not. Operationally this matters because home-grown parsers and some middleware mangle multi-challenge responses, so many servers emit one challenge per header line, and browsers historically prefer `Negotiate` over `Basic` when both are offered.

code

http · 6 lines
http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate
WWW-Authenticate: Basic realm="intranet", charset="UTF-8"

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate, Basic realm="intranet", charset="UTF-8"

go deeper

for a junior

Know that a 401 can offer several schemes and the client answers only one of them.

for a middle

Explain the scheme plus token68 or auth-params grammar, case-insensitive names, and that repeated header lines are equivalent to a combined comma list.

for a senior

Show the parsing hazard concretely, the practice of one challenge per line, client preference behaviour, and how multi-round schemes break naive retry logic.

for a principal

Reason about advertising a scheme set at all — migration windows, fallback that silently downgrades security, and the governance value of the IANA registry over bespoke scheme names.

## Why multiple challenges exist A server may support more than one way in: Kerberos/SPNEGO for domain-joined clients with a `Basic` fallback for everyone else, or a bearer scheme alongside a legacy one during a migration. RFC 9110 lets a 401 advertise all of them at once so the client can choose without extra round trips. ## The grammar, precisely `WWW-Authenticate` is a list of **challenges**. Each challenge is: - an `auth-scheme` token (case-insensitive: `basic` equals `Basic`), then optionally - a single **token68** value — an opaque blob of base64-ish characters with optional `=` padding, used by schemes like `Negotiate` and `Bearer` for raw data — **or** - a comma-separated list of **auth-params**, `name=token` or `name="quoted string"`. A challenge may also be bare, with nothing after the scheme (`WWW-Authenticate: Negotiate`). ## Why splitting on commas fails Consider: `WWW-Authenticate: Negotiate, Basic realm="eng, ops", charset="UTF-8"` Naive comma splitting yields four fragments, two of which are meaningless. A correct parser must: 1. respect **quoted strings**, including commas and escaped quotes inside them; 2. decide, at each list element, whether a leading token is a *new scheme* or a *parameter of the current challenge* — the discriminator is essentially whether the token is followed by `=`; 3. accept **repeated header fields**, since `WWW-Authenticate: Negotiate` on one line and `WWW-Authenticate: Basic realm="x"` on another is equivalent to the single combined line; 4. tolerate optional whitespace around commas and equals signs. This ambiguity is a known wart of the grammar, and it is why so much middleware misbehaves. The defensive server-side habit is to **emit one challenge per header field line** rather than one combined line, and to avoid commas inside realm strings. ## Choosing a scheme The specification says the client selects the challenge it considers most secure among those it understands; order in the response is *not* a priority signal, though many servers list strongest first out of habit. In practice: - Browsers prefer `Negotiate` over `NTLM` over `Digest` over `Basic` when several are offered, and will only show a username/password prompt if the chosen scheme needs one. - HTTP libraries vary: some pick the first recognised challenge, some the last, and some require you to name the scheme up front. `curl --anyauth` inspects the challenge and picks; `curl --basic` forces one. - Only **one** `Authorization` header may be sent. Multiple credential sets are not how the framework works — pick a scheme, answer that challenge. ## Multi-round schemes Some schemes are not one-shot. `Negotiate` exchanges tokens over several 401s, each carrying an updated token68 in `WWW-Authenticate`, until it succeeds or fails. The final success response may carry `Authentication-Info` (or a scheme-specific field) with data the client uses to authenticate the *server*. A client that treats any 401 as terminal cannot complete such a handshake — which is why "retry once then give up" logic breaks SPNEGO. ## The scheme registry Scheme names live in the IANA **HTTP Authentication Scheme Registry** (`Basic`, `Digest`, `Bearer`, `Negotiate`, `HOBA`, `Mutual`, `SCRAM-SHA-256`, `vapid`, ...). Registration is what keeps names collision-free across the ecosystem, and it is why a challenge is self-describing: any client can look up a name it does not implement. Inventing an unregistered scheme name is legal on the wire but means no generic client will ever understand it. ## Failure handling If the client supports **none** of the offered schemes, it must not guess — it surfaces the 401 as an error. If it answers and gets 401 again, the credentials or the scheme choice were wrong; a retry loop with the same inputs is pointless and, for password schemes, walks straight into account lockout. Sensible clients bound retries and surface the challenge list in the error so an operator can see what the server actually offered.

  • Does the order of challenges in the response tell the client which to prefer?
    No. The specification says the client chooses the challenge it considers most secure among the schemes it supports, so ordering carries no normative weight. Servers often list the strongest first as a hint, and some clients do take the first recognised one, so ordering is a practical nudge rather than a contract.
  • Can a client send two Authorization headers to answer two challenges?
    No. Exactly one set of credentials for the origin is sent, in a single Authorization header, answering the one scheme the client chose. Multiple credential headers are undefined and will typically be rejected or silently reduced to one by intermediaries and servers.

saying these in an interview costs you the question

  • Splitting the header value on commas and treating every fragment as a scheme
  • Assuming the first challenge listed is mandatory or preferred by specification
  • Sending two Authorization headers to satisfy two challenges
  • Treating scheme names as case-sensitive, so 'bearer' is deemed invalid
  • Giving up after one 401 in a multi-round scheme such as Negotiate

context