Encoding
Strict base64url and base64 codecs with the JOSE rules fixed in, plus the byte, text and randomness helpers that go with them.
import { base64UrlParse, base64UrlStringify } from "unjwt/utils";
base64UrlStringify('{"alg":"HS256","typ":"JWT"}'); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
base64UrlParse("aGVsbG8"); // "hello"Every segment of a JWT is base64url text. These helpers are the codecs from unsecure with the alphabet and padding rules pinned, so you cannot call them with a policy that disagrees with the one verify() and decrypt() apply.
#Signatures
function base64UrlStringify(data: string | BytesSource): string;
function base64UrlParse(
input: string | Uint8Array,
options?: { returnAs?: "string" | "uint8array" | "bytes"; loose?: boolean },
): string | Uint8Array<ArrayBuffer>;
function base64Stringify(data: string | BytesSource): string;
function base64Parse(
input: string | Uint8Array,
options?: { returnAs?: "string" | "uint8array" | "bytes"; loose?: boolean },
): string | Uint8Array<ArrayBuffer>;BytesSource is an ArrayBuffer or any ArrayBufferView. A string passed to a *Stringify function is UTF-8 encoded first.
#Options
| Option | Type | Default | What it does |
|---|---|---|---|
returnAs | "string" | "uint8array" | "bytes" | mirrors the input | "string" decodes the bytes as UTF-8. "uint8array" and its alias "bytes" return the raw bytes. |
loose | boolean | false | Drop characters the alphabet does not carry instead of throwing. For application data only. |
#What unjwt adds to the unsecure codecs
| Function | Alphabet | Padding |
|---|---|---|
base64UrlStringify | URL-safe -_ | never written |
base64UrlParse | URL-safe -_ only | any = is rejected |
base64Stringify | standard +/ | always written |
base64Parse | standard +/ only | the = run is required |
The URL-safe pair follows RFC 7515 §2: no padding, no whitespace, no line breaks. The standard pair follows RFC 4648 §4 and is the one PEM bodies use.
#Return shape
| You passed | You get back |
|---|---|
a string | a UTF-8 string, which must be valid UTF-8 |
a Uint8Array of the encoded text | a Uint8Array<ArrayBuffer> |
anything, plus returnAs | exactly what returnAs names |
import {
base64Parse,
base64Stringify,
base64UrlParse,
base64UrlStringify,
textEncoder,
} from "unjwt/utils";
base64UrlStringify("hello"); // "aGVsbG8"
base64UrlStringify(new Uint8Array([255, 255])); // "__8"
base64Stringify("hello"); // "aGVsbG8="
base64Stringify(new Uint8Array([255, 255])); // "//8="
base64UrlParse("aGVsbG8", { returnAs: "bytes" }); // Uint8Array(5) [104, 101, 108, 108, 111]
base64UrlParse(textEncoder.encode("aGVsbG8")); // Uint8Array(5) [104, 101, 108, 108, 111]
base64UrlParse(textEncoder.encode("aGVsbG8"), { returnAs: "string" }); // "hello"
base64Parse("aGVsbG8="); // "hello"When the decoded bytes are not text, ask for bytes. Decoding "__8" to a string throws, because 0xFF 0xFF is not UTF-8.
base64UrlParse("__8", { returnAs: "bytes" }); // Uint8Array(2) [255, 255]
base64UrlParse("__8");
// UnsecureError MALFORMED: Base64.parse: decoded bytes are not valid UTF-8.#Strict by default
parse accepts only the canonical encoding of a byte string. Two different texts that decode to the same bytes are how signature checks get bypassed, so anything else is refused.
base64UrlParse("aGVsbG8=");
// UnsecureError MALFORMED: Base64.parse: unexpected "=" padding.
base64UrlParse("a+Vs");
// UnsecureError MALFORMED: Base64.parse: invalid base64 character "+" at index 1.
base64UrlParse("aGVs bG8");
// UnsecureError MALFORMED: Base64.parse: invalid base64 character " " at index 4.
base64UrlParse("aGVsbG9");
// UnsecureError MALFORMED: Base64.parse: the last base64 symbol sets bits past the final byte.
base64Parse("aGVsbG8");
// UnsecureError MALFORMED: Base64.parse: expected 1 "=" padding characters, found 0.
base64Parse("__8=");
// UnsecureError MALFORMED: Base64.parse: invalid base64 character "_" at index 0.{ loose: true } drops whitespace, = and any character outside the alphabet. Use it for values a person pasted. verify() and decrypt() have no such option, and token material should never go through it.
base64UrlParse("aGVs bG8=", { loose: true }); // "hello"#Bytes and text
concatUint8Arrays(...arrays) joins byte arrays into one new buffer. maybeArray(value) wraps a value in an array unless it already is one. textEncoder and textDecoder are shared module-level instances from unsecure/utils, so you do not allocate one per call.
import {
base64UrlStringify,
concatUint8Arrays,
maybeArray,
textDecoder,
textEncoder,
} from "unjwt/utils";
const signingInput = concatUint8Arrays(
textEncoder.encode(base64UrlStringify('{"alg":"HS256"}')),
textEncoder.encode("."),
textEncoder.encode(base64UrlStringify("hi")),
);
textDecoder.decode(signingInput); // "eyJhbGciOiJIUzI1NiJ9.aGk"
concatUint8Arrays(new Uint8Array([1, 2]), new Uint8Array([3])); // Uint8Array(3) [1, 2, 3]
maybeArray("api"); // ["api"]
maybeArray(["api", "web"]); // ["api", "web"]#secureRandomBytes()
secureRandomBytes(length) returns length bytes from the platform CSPRNG. It is the unsecure/random function, re-exported unchanged. Use it for salts, IVs and raw secrets, never Math.random().
import { base64UrlStringify, secureRandomBytes } from "unjwt/utils";
const salt = secureRandomBytes(16); // Uint8Array(16)
base64UrlStringify(salt); // "HZVurVz1r5er…"
secureRandomBytes(-1);
// UnsecureError OUT_OF_RANGE: secureRandomBytes: length must be an integer between 0 and 2147483647, got -1.#Errors
These helpers come from unsecure and throw its UnsecureError directly, not a JWTError.
| Cause | code |
|---|---|
| Text that is not the canonical encoding, or a decoded string that is not UTF-8 | MALFORMED |
A value that is neither text nor bytes, such as null | INVALID_TYPE |
A secureRandomBytes length that is negative, fractional or too large | OUT_OF_RANGE |
Check with isUnsecureError imported from unjwt. unjwt bundles its own copy of unsecure, so an UnsecureError class imported from your own unsecure dependency is a different class and instanceof against it never matches.
#See also
- unsecure codecs: the full strict and loose contract, plus hex and base32.
- Type guards:
isUnsecureErrorandisJWTError. - Errors: how a decode failure inside
verify()surfaces as aJWTErrorwith theUnsecureErrorascause.