try hiccup

SIP 401 vs 407

Short version: one 401 or 407 followed by a credentialled retry is not a failure — that is how SIP Digest authentication is supposed to look. The failure mode is the loop: challenge, credentialled retry, challenge again. And a loop has more causes than a wrong password.

401 vs 407: who is challenging you

The two codes carry the same mechanism but name a different challenger. A 401 Unauthorized comes from the server that will consume the request itself — a registrar answering your REGISTER, or a UAS answering your INVITE. It carries WWW-Authenticate and expects the retry to carry Authorization. A 407 Proxy Authentication Required comes from a proxy the request was passing through; it carries Proxy-Authenticate and expects Proxy-Authorization. RFC 3261 reserves 401 for registrars and UASs and 407 for proxies — hence the shorthand, right far more often than not: 401 means registration, 407 means a call.

Several proxies on the path may each challenge, so Proxy-Authorization headers accumulate: each proxy takes the credentials for its own realm and forwards the rest. Two requests are never challenged at all — RFC 3261 section 22.1 rules out challenging a CANCEL (it cannot be resubmitted with credentials), and an ACK cannot be answered, so it carries a copy of the INVITE's credentials instead.

Digest auth on the wire

The challenge names a realm, supplies a server-chosen nonce, and usually a qop and an algorithm. The client never sends the password: it hashes username:realm:password into one value, method:uri into another, combines them with the nonce — plus, when the challenge names a qop, a client nonce (cnonce) and a per-nonce request counter (nc) — and returns the result in response=. The server does the same arithmetic and compares. The scheme is HTTP Digest — RFC 2617, revised by RFC 7616; RFC 8760 brought the SHA-256 variants to SIP, though in the field MD5 still dominates.

Two parameters do most of the diagnostic work. stale=true on a repeat challenge means "your credentials were fine, but the nonce was not — take this fresh one and retry without re-prompting for a password". And nc must increment each time a nonce is reused — that is the replay check; a retransmission keeps its nc, a new request must not.

One challenge is normal

Clients send the first request bare on purpose — they cannot compute a response before they have seen a nonce. The healthy shapes:

REGISTER            →  401 (WWW-Authenticate: nonce N)
REGISTER + Authorization (nonce N)   →  200 OK

INVITE              →  407 (Proxy-Authenticate: nonce N)
ACK                                      (ends that transaction)
INVITE + Proxy-Authorization, CSeq+1  →  100 / 180 / 200

On the INVITE side the 407 is a final response, so it is ACKed, and the credentialled INVITE is a new request — same Call-ID, CSeq incremented by one. Dashboards that count raw 4xx report a "failure" on every outbound call through a challenging proxy; filter these pairs out before believing an error rate.

What actually causes auth loops

The credentials really are wrong — but check which username

Still the most common cause, with a twist: most gear separates the auth username from the user part of the From and To URIs, and it is the auth username the server checks. A PBX trunk with the account number in the From but the wrong value in the auth-user field loops on 407 against a perfectly correct password. Usernames are often case-sensitive, and passwords containing & or % get mangled by provisioning layers more often than anyone admits.

Stale nonces

Servers expire nonces — some after minutes, some after a single use. A client that computes correctly but answers with a nonce the server no longer honours gets challenged again, ideally with stale=true; a well-behaved client silently retries with the new nonce and the loop converges. The pathological version is a client replaying an old nonce on every new request, or a server enforcing nc strictly against a client that always sends nc=00000001. Either way, the tell is in the parameters, not the password.

A cluster that does not share nonce state

Two registrars behind a round-robin load balancer, nonces held in local memory: server A mints the challenge, server B receives the answer and has never seen that nonce. Every response is rejected as stale no matter how correct it is. The signature is unmistakable — an endless loop in which every challenge says stale=true with a fresh nonce, while the client's responses are properly formed.

Realm mismatch

The password hash is bound to the realm — username:realm:password — so anything that changes the realm invalidates every stored hash. Servers that store pre-computed hashes cannot simply rename their realm, and a failover server presenting a different realm breaks clients that cached credentials against the old one. If the realm in the challenge is not the realm the client was provisioned with, no password will ever work.

Something rewrote the request in flight

The digest covers the request method and URI, and strict servers verify that the uri= parameter in the credentials matches the Request-URI they actually received. A SIP ALG on a router, or an SBC rewriting the Request-URI in transit, silently breaks that comparison: the client signed one URI, the server received another. Both ends are behaving correctly — the middlebox is the culprit.

Algorithm disagreement

Since RFC 8760 a server may offer several challenges, strongest first — SHA-256, then MD5. An older client may ignore the unfamiliar challenge outright, or answer it with MD5 arithmetic. Rare, but worth a glance at algorithm= when everything else checks out.

Registration auth vs per-call auth

Registration authenticates one thing only: the binding between an address-of-record and a contact. It does not pre-authorise anything that follows, so most deployments challenge INVITEs separately — which is why a phone can register happily and still hit a 407 on every call. Carrier trunks often skip Digest entirely and authenticate by source IP address; there, a credential problem never appears as a 401/407 loop but as a straight 403 Forbidden. Many servers also convert repeated Digest failure into a 403 after two or three attempts, so the end of an auth loop and an outright ban look identical in the CDR.

What to check in the trace

  1. Confirm the retry is real: same Call-ID, CSeq incremented, and an Authorization or Proxy-Authorization header actually present. A client repeating the identical bare request never understood the challenge.
  2. Compare the nonce in the credentials against the most recent challenge. A client replaying an older nonce is a client problem; a server refusing its own fresh nonce is a server problem.
  3. Read stale= on the second challenge. stale=true means the credentials were accepted and the nonce was not — stop investigating the password. Every challenge stale with a fresh nonce, forever, points at a cluster not sharing nonce state.
  4. Match realm= in the credentials against realm= in the challenge, character for character.
  5. Match uri= against the Request-URI on the wire at the point of capture. A mismatch means something in between rewrote the request — go find the ALG or SBC.
  6. Check username= against what the server keys accounts on — where the auth-user-versus-From-user confusion becomes visible.
  7. Watch how the loop ends. A 403 after a few rounds is the server giving up, often with the real reason in the reason phrase. Sudden silence, retransmissions and a local 408 mean a fail2ban-style blocker is now dropping your packets — the auth loop was the trigger.

hiccup pairs every challenge with its credentialled retry, collapses the healthy 401/407 handshakes out of the failure count, and flags nonce reuse, realm mismatches and stale loops as findings — next to the ladder. Self-hosted, free for individual users.

upload a trace