Skip to content

Basic Auth Generator — TypeScript source

Build HTTP Basic Access Authentication headers from a username and password, or decode an existing Authorization header back to its credentials. UTF-8 safe and fully client-side, with a shareable link.

This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Pure HTTP Basic-Auth header builder/parser. Zero deps - reuses the UTF-8
// safe base64 helpers from src/lib/base64.ts. The unit-test surface for the
// Basic Auth Generator tool.

import { b64encode, b64decode } from './base64';

export interface BasicAuthCredentials {
  user: string;
  pass: string;
}

/** Build an HTTP Basic-Auth header value: `Basic <base64(user:pass)>`. */
export function buildBasicAuth(user: string, pass: string): string {
  return 'Basic ' + b64encode(`${user}:${pass}`);
}

/** Parse a `Basic <token>` header back into credentials, or null if malformed. */
export function parseBasicAuth(header: string): BasicAuthCredentials | null {
  const m = header.trim().match(/^Basic\s+(.+)$/i);
  if (!m) return null;
  let creds: string;
  try {
    creds = b64decode(m[1]);
  } catch {
    return null;
  }
  const idx = creds.indexOf(':');
  if (idx < 0) return null;
  return { user: creds.slice(0, idx), pass: creds.slice(idx + 1) };
}

export type BasicAuthIssueCode =
  | 'empty-username'
  | 'colon-in-username'
  | 'password-non-ascii';

export interface BasicAuthIssue {
  code: BasicAuthIssueCode;
  severity: 'warn' | 'info';
  value?: string;
}

/**
 * Lint credentials before they ship. All three checks are 'warn':
 * - empty-username: servers usually accept it, but logs and proxies get confusing
 * - colon-in-username: the user:pass split happens at the FIRST colon, so a
 *   colon in the username is silently truncated by every conforming parser
 * - password-non-ascii: RFC 7617 defaults to UTF-8 but many servers assume
 *   ISO-8859-1/US-ASCII and mangle or reject non-ASCII bytes
 */
export function validateBasicAuth(creds: BasicAuthCredentials): BasicAuthIssue[] {
  const issues: BasicAuthIssue[] = [];
  if (creds.user === '') {
    issues.push({ code: 'empty-username', severity: 'warn' });
  }
  if (creds.user.includes(':')) {
    issues.push({ code: 'colon-in-username', severity: 'warn', value: creds.user });
  }
  if (/[^\x00-\x7F]/.test(creds.pass)) {
    issues.push({ code: 'password-non-ascii', severity: 'warn', value: creds.pass });
  }
  return issues;
}

// URL codecs for shareable state. Each component is encodeURIComponent'd on
// the way out; the two flat strings are independent params (no ',' '|' '='
// grammar needed). Malformed escapes decode verbatim rather than throw.

const enc = (s: string) => encodeURIComponent(s);
const dec = (s: string): string => {
  try {
    return decodeURIComponent(s);
  } catch {
    return s; // malformed escape - keep verbatim rather than throw
  }
};

export function toQuery(creds: BasicAuthCredentials): string {
  const p = new URLSearchParams();
  if (creds.user !== '') p.set('u', enc(creds.user));
  if (creds.pass !== '') p.set('p', enc(creds.pass));
  return p.toString();
}

export function fromQuery(params: URLSearchParams): BasicAuthCredentials | null {
  const u = params.get('u');
  const p = params.get('p');
  if (u === null && p === null) return null;
  return { user: u === null ? '' : dec(u), pass: p === null ? '' : dec(p) };
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →