Skip to content

Basic Auth Generator — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// basic-auth-generator — HTTP Basic-Auth header encode/decode.
//
// Language: Swift (5.9, standard library + Foundation)
// Source:   CosmoDev polyglot showcase port of the Basic Auth Generator tool,
//           ported from src/lib/basic-auth.ts (the canonical TypeScript
//           implementation) and kept in lock-step with cli/basic-auth-generator.
// License:  display source — part of CosmoDev's polyglot tool pages.
//
// Design goals:
//   - Pure + deterministic; never traps (parseHeader returns nil on
//     malformed input).
//   - Functionally equivalent to the TS reference + Go twin: same inputs ->
//     same outputs, same reject behavior on malformed headers.
//   - Self-contained: stdlib + Foundation only (Data supplies the UTF-8 byte
//     bridge that Swift's String API needs).
//
// Codec note: Foundation's Data.base64EncodedString() provides the RFC 4648
// standard encoder used by every other port. For decoding, Data(base64Encoded:)
// has historically ignored unknown characters on some platforms, so — like the
// Rust port — a strict decoder is implemented inline: whitespace is stripped,
// the length must be a multiple of 4, and every character must be in the
// standard alphabet or count as '=' padding.

import Foundation

/// Credentials hold a parsed username/password pair. Mirrors the Go twin's
/// `Credentials` struct and the TS `BasicAuthCredentials` interface.
struct Credentials: Equatable {
    var user: String
    var pass: String
}

/// RFC 4648 §4 standard base64 alphabet, as UTF-8 bytes.
private let b64Alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".utf8)

/// ASCII whitespace skipped by the decoder: space (0x20), tab (0x09),
/// line feed (0x0A), carriage return (0x0D) — the same set Rust's
/// `u8::is_ascii_whitespace` strips.
private func isSkippedWhitespace(_ byte: UInt8) -> Bool {
    byte == 0x20 || byte == 0x09 || byte == 0x0A || byte == 0x0D
}

/// Reverse lookup: value of one base64 digit, or nil if not in the alphabet.
private func b64Value(_ byte: UInt8) -> UInt8? {
    if let idx = b64Alphabet.firstIndex(of: byte) {
        return UInt8(idx)
    }
    return nil
}

/// Build an HTTP Basic-Auth header value: `"Basic " + base64(user:pass)`.
/// Mirrors `BuildHeader` in the Go twin and `buildBasicAuth` in the TS source.
/// Data(string.utf8) carries the UTF-8 byte representation, so multibyte
/// credentials (e.g. `café:päss`) encode correctly.
func buildHeader(user: String, pass: String) -> String {
    let raw = Data("\(user):\(pass)".utf8)
    return "Basic " + raw.base64EncodedString()
}

/// Decode standard base64 strictly into bytes. Returns nil on any invalid
/// character or malformed padding, matching Go's
/// `base64.StdEncoding.DecodeString`. Whitespace inside the token is ignored,
/// mirroring the TS `b64decode(token.replace(/\s+/g, ''))` helper.
private func b64Decode(_ input: String) -> Data? {
    let cleaned = input.utf8.filter { !isSkippedWhitespace($0) }
    if cleaned.count % 4 != 0 { return nil }

    var out = [UInt8]()
    out.reserveCapacity(cleaned.count / 4 * 3)

    var i = 0
    while i < cleaned.count {
        var idx = [UInt8](repeating: 0, count: 4)
        var pads = 0
        for j in 0..<4 {
            let byte = cleaned[i + j]
            if byte == 0x3D { // '='
                pads += 1
            } else if let v = b64Value(byte) {
                idx[j] = v
            } else {
                return nil // invalid character
            }
        }
        let n = (UInt32(idx[0]) << 18) | (UInt32(idx[1]) << 12)
            | (UInt32(idx[2]) << 6) | UInt32(idx[3])
        out.append(UInt8(truncatingIfNeeded: n >> 16))
        if pads < 2 { out.append(UInt8(truncatingIfNeeded: n >> 8)) }
        if pads < 1 { out.append(UInt8(truncatingIfNeeded: n)) }
        i += 4
    }
    return Data(out)
}

/// Parse a `"Basic <token>"` header back into credentials, or nil if the
/// header is malformed or the credentials have no colon separator. Logic
/// mirrors `ParseHeader` in the Go twin exactly: trim the header, require a
/// case-insensitive `"basic "` prefix, trim the remaining token, base64-decode
/// it (rejecting invalid characters), UTF-8-validate, then split on the FIRST
/// colon so a password containing colons round-trips.
func parseHeader(_ header: String) -> Credentials? {
    let h = header.trimmingCharacters(in: .whitespacesAndNewlines)
    guard h.lowercased().hasPrefix("basic ") else { return nil }

    let token = String(h.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
    guard let data = b64Decode(token) else { return nil }
    // String(data:encoding:) is nil on invalid UTF-8 — the same strict
    // validation Rust's String::from_utf8 performs.
    guard let decoded = String(data: data, encoding: .utf8) else { return nil }

    // Split on the FIRST colon only (maxSplits: 1) — the rest stays in the
    // password. Empty halves are kept so ("", "x") and ("x", "") round-trip.
    let parts = decoded.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
    guard parts.count == 2 else { return nil }
    return Credentials(user: String(parts[0]), pass: String(parts[1]))
}

/// Showcase-only demo (the canonical suite lives in src/lib): runs the same
/// vectors as the Rust port. Swift files other than main.swift cannot hold
/// top-level code, so the examples live in a callable run() instead of a
/// main() — invoke `BasicAuthDemo.run()` from a host program or REPL.
enum BasicAuthDemo {
    static func run() {
        let h = buildHeader(user: "user", pass: "pass")
        precondition(h == "Basic dXNlcjpwYXNz", "known vector mismatch: \(h)")
        print(h)

        let roundTrips: [(String, String)] = [
            ("alice", "s3cr3t"), ("", "x"), ("x", ""), ("u", "a:b:c"),
        ]
        for (user, pass) in roundTrips {
            guard let creds = parseHeader(buildHeader(user: user, pass: pass)) else {
                preconditionFailure("round trip should parse for (\(user), \(pass))")
            }
            precondition(creds.user == user, "user mismatch for (\(user), \(pass))")
            // colon-bearing password stays intact: split happens on the first colon only
            precondition(creds.pass == pass, "password mismatch for (\(user), \(pass))")
        }

        let malformed = ["Bearer xyz", "", "Basic !!!not-base64!!!", "Basic bm9jb2xvbg=="]
        for bad in malformed {
            precondition(parseHeader(bad) == nil, "\(bad) should be rejected")
        }
        print("all basic-auth-generator demo checks passed")
    }
}

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 →