billpg.WWWAuthenticateTools 0.1.0

dotnet add package billpg.WWWAuthenticateTools --version 0.1.0
                    
NuGet\Install-Package billpg.WWWAuthenticateTools -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="billpg.WWWAuthenticateTools" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="billpg.WWWAuthenticateTools" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="billpg.WWWAuthenticateTools" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add billpg.WWWAuthenticateTools --version 0.1.0
                    
#r "nuget: billpg.WWWAuthenticateTools, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package billpg.WWWAuthenticateTools@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=billpg.WWWAuthenticateTools&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=billpg.WWWAuthenticateTools&version=0.1.0
                    
Install as a Cake Tool

WWWAuthenticateTools

Parser and Builder classes for dealing with the WWW-Authenticate HTTP header.

๐Ÿค” Why This Exists

The WWW-Authenticate grammar is more subtle than it looks. A single header value can carry multiple challenges, comma-separated โ€” but the auth-params within a challenge are also comma-separated, so telling "another param for this challenge" from "a new challenge starts here" requires a two-token lookahead, not a simple Split(','). On top of that, WWW-Authenticate is one of the header fields RFC 9110/7230 explicitly says can't always be safely combined into one line by joining multiple header field instances with commas โ€” so a correct parser has to accept a collection of raw header values, not a single pre-joined string. It's easy to write something that handles the common case and quietly breaks on real-world multi-challenge or multi-instance headers, which is how this problem ends up solved badly, piecemeal, in thousands of codebases instead of correctly, once.

Existing libraries were surveyed and found wanting:

  • Python's www-authenticate (PyPI): parses to an OrderedDict, has no generator, uses fragile comma-splitting, and has been unmaintained since ~2014.
  • npm's www-authenticate: explicitly documents that it does not support headers with more than one challenge.
  • .NET's built-in AuthenticationHeaderValue: models a single scheme plus one opaque parameter string; it doesn't decompose multi-challenge headers at all.

None of them combine correct multi-challenge parsing, a generator, and a design that ports cleanly across languages.

This is the C# port of a planned cross-language library (C#/Python/TypeScript) for parsing and generating WWW-Authenticate, Proxy-Authenticate, Authorization, and Proxy-Authorization header values, per RFC 9110 ยง11.6.1 and ยง11.3. The library works purely at the string level โ€” no dependency on any HTTP framework.

๐Ÿ—‚๏ธ Project Layout

  • billpg.WWWAuthenticateTools/ โ€” the library itself. Target framework is netstandard2.0 for broad compatibility.
  • billpg.WWWAuthenticateToolsTests/ โ€” MSTest unit tests. Per project convention, test assemblies are named billpg.(project)Tests.
  • billpg.WWWAuthenticateTools/billpg.WWWAuthenticateTools.slnx โ€” the solution file, referencing both projects.
  • test-vectors/ โ€” git submodule pointing at www-authenticate-test-vectors, the shared cross-language test vector set. Pinned to a tag, not tracking its main branch.

๐Ÿš€ Getting Started

git submodule update --init --recursive   # first time after cloning
dotnet build billpg.WWWAuthenticateTools/billpg.WWWAuthenticateTools.slnx
dotnet test billpg.WWWAuthenticateTools/billpg.WWWAuthenticateTools.slnx

๐Ÿ“ฆ API Overview

The data model is immutable throughout: Challenge (one auth-scheme plus either a token68 or an ordered list of name/value params) and AuthHeaders (an ordered collection of Challenge).

Build headers with the fluent builder โ€” each call returns a new instance, and WithParam/WithToken68 apply to whichever scheme was added most recently:

var auth = new AuthHeaders()
    .WithScheme("HashBack")
    .WithParam("version", "RFC12345")
    .WithScheme("Basic")
    .WithParam("realm", "example");

Generate header text from a model:

auth.ToSingleHeaderValue();      // "HashBack version=RFC12345, Basic realm=example"
auth.ToHeaderLines();            // one string per challenge

Parse raw header values back into a model. The entry point takes one string per actual header line received (not a single pre-joined string), since WWW-Authenticate is one of the header fields that can't always be safely combined by comma-joining multiple instances:

var parsed = ParseHeader.Parse(["Basic realm=example"], strict: true);

โš ๏ธ Exceptions

Every exception carries a stable string Code, which is the cross-language contract other ports of this library will also use (message text is not):

  • AuthHeaderException โ€” abstract base.
  • AuthHeaderParseException โ€” malformed input during parsing; also carries HeaderLineIndex and CharacterPosition.
  • AuthHeaderBuilderException โ€” invalid use of the fluent builder.

The documented codes live in AuthHeaderErrorCodes: no_current_scheme, duplicate_param, token68_param_conflict, invalid_token68, invalid_auth_param, unterminated_quoted_string, unexpected_comma.

๐Ÿšง Status

Implemented: the data model, immutable fluent builder, exception taxonomy, generator, a strict-mode parser for RFC-conformant input, one lenient-mode deviation (control characters in quoted-string values โ€” see Design Notes below), and the test-vectors submodule wired into TestVectorTests.cs (conformant, lenient, and invalid category, parse-direction vectors).

Not yet implemented:

  • The rest of lenient-mode parsing โ€” every other documented deviation still behaves identically to strict mode.
  • direction: build vectors (builder call sequences) from the shared suite.
  • Python and TypeScript ports.

๐ŸŽฏ Design Notes

  • Params are kept in an ordered list, not a dictionary, so round-tripping preserves the original order even though lookups are logically case-insensitive by name.
  • The builder does not re-validate RFC token grammar (that's the parser's job for untrusted input) โ€” it only enforces structural invariants: a scheme must exist before WithParam/WithToken68, param names must be unique per challenge, and WithParam/WithToken68 are mutually exclusive on the same challenge.
  • Parsing a challenge's second word (right after the scheme) is ambiguous between a token68 and the first auth-param โ€” e.g. Bearer abc123== vs Digest realm=example. The parser resolves this the same way for every subsequent comma-separated item too: an auth-param is always token BWS "=" BWS value, so anything that isn't genuinely shaped that way (including a token68's own trailing == padding, which isn't a real name=value split) is treated as a new challenge or a token68 instead.
  • A control character (other than HTAB) inside a quoted-string value isn't valid per RFC 9110 ยง5.6.4's qdtext/quoted-pair grammar, raw or backslash-escaped. strict: true rejects it (invalid_auth_param); strict: false substitutes a space rather than passing it through โ€” otherwise a value containing a raw CR/LF, round-tripped through Generate, could smuggle an extra header into the output stream.
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 47 9/4/2026