WWW-Authenticate Header Parser
Parse WWW-Authenticate challenges and quoted auth parameters, including multiple challenges in one header.
Code
export interface AuthChallenge {
scheme: string;
params: Record<string, string>;
}
function skipWhitespace(header: string, index: number): number {
while (index < header.length && /\s/.test(header[index]!)) index += 1;
return index;
}
function readToken(header: string, index: number): { value: string; end: number } {
const match = /^[^\s,="]+/.exec(header.slice(index));
if (!match) throw new SyntaxError("expected token at position " + index);
return { value: match[0], end: index + match[0].length };
}
function readQuoted(header: string, index: number): { value: string; end: number } {
let value = "";
index += 1;
while (index < header.length) {
const character = header[index]!;
if (character === "\\" && index + 1 < header.length) {
value += header[index + 1];
index += 2;
continue;
}
if (character === '"') return { value, end: index + 1 };
value += character;
index += 1;
}
throw new SyntaxError("unterminated quoted string");
}
function readValue(header: string, index: number): { value: string; end: number } {
return header[index] === '"' ? readQuoted(header, index) : readToken(header, index);
}
export function parseWwwAuthenticate(header: string): AuthChallenge[] {
if (typeof header !== "string" || header.trim() === "") {
throw new TypeError("header must be a non-empty string");
}
const challenges: AuthChallenge[] = [];
let index = 0;
while (index < header.length) {
index = skipWhitespace(header, index);
if (header[index] === ",") {
index = skipWhitespace(header, index + 1);
continue;
}
if (index >= header.length) break;
const schemeToken = readToken(header, index);
const scheme = schemeToken.value;
index = skipWhitespace(header, schemeToken.end);
const params: Record<string, string> = {};
if (index < header.length && header[index] !== ",") {
const name = readToken(header, index);
index = skipWhitespace(header, name.end);
if (header[index] !== "=") {
throw new SyntaxError("expected = after parameter " + name.value);
}
index = skipWhitespace(header, index + 1);
const value = readValue(header, index);
params[name.value.toLowerCase()] = value.value;
index = value.end;
}
while (true) {
index = skipWhitespace(header, index);
if (header[index] !== ",") break;
const afterComma = skipWhitespace(header, index + 1);
if (afterComma >= header.length) {
index = afterComma;
break;
}
const lookahead = readToken(header, afterComma);
const afterName = skipWhitespace(header, lookahead.end);
if (header[afterName] !== "=") break;
index = skipWhitespace(header, afterName + 1);
const value = readValue(header, index);
params[lookahead.value.toLowerCase()] = value.value;
index = value.end;
}
challenges.push({ scheme, params });
}
return challenges;
}Report a problem with this exampleOpens a pre-filled issue on GitHub.