[READ-ONLY] Mirror of https://github.com/sandros94/unjwt. A collection of JWT utilities based on the Web Crypto API, in a single zero-depend unjwt.s94.dev
auth browser decrypt edge encrypt jose jwe jwk jws jwt security serverless session sign verify webcrypto
unjwt docs 3.utils 2.guards.md
6.7 kB · 182 lines
Markdown
at main


Type guards #

Functions that answer "what is this value?" about keys, key sets, headers and errors, and narrow its TypeScript type when the answer is yes.

import { isPublicJWK } from "unjwt/utils";

const shareable = jwks.keys.filter(isPublicJWK);

A JWK is a plain JSON object, so at runtime nothing tells a public key from a private one except the fields it carries. Each guard checks those fields and returns a boolean. In TypeScript it also narrows the value, so code after the check sees the precise key type.

The examples on this page use these keys:

import { generateJWK, generateKey } from "unjwt/jwk";

const hs = await generateJWK("HS256"); // { kty: "oct", k: "cSc6O7FW…", alg: "HS256", … }
const { privateKey: esPrivate, publicKey: esPublic } = await generateJWK("ES256");
const cryptoKey = await generateKey("HS256"); // CryptoKey
const pair = await generateKey("ES256"); // CryptoKeyPair

At a glance #

Guard Narrows to True when
isJWK(value) JWK an object whose kty is oct, RSA, EC or OKP
isJWKSet(value) JWKSet an object with a keys array
isSymmetricJWK(value) JWK_oct a JWK with kty: "oct" and a string k
isAsymmetricJWK(value) Exclude<JWK, JWK_oct> a JWK whose kty is RSA, EC or OKP
isPrivateJWK(value) JWK_Private an RSA, EC or OKP JWK with a string d
isPublicJWK(value) JWK_Public an RSA, EC or OKP JWK with its public fields and no d or k
isCryptoKey(value) CryptoKey a Web Crypto key, from any realm
isCryptoKeyPair(value) CryptoKeyPair an object whose publicKey and privateKey are both CryptoKeys
assertCryptoKey(value) asserts value is CryptoKey throws ERR_JWK_INVALID otherwise
isJWTContent(header) boolean, no narrowing typ names JWT, or cty names JSON
isJWTError(error, code?) JWTError an unjwt error, optionally with that code
isUnsecureError(error, code?) UnsecureError an error from the bundled unsecure primitives

isJWK #

Recognises the four key types this library models. A JWK with any other kty is refused, so the narrowed type is always one unjwt can use.

isJWK(esPrivate); // true
isJWK({ kty: "foo" }); // false
isJWK("secret"); // false

isJWKSet #

Checks the shape { keys: [...] } and nothing more. It does not look inside the array, so filter the entries with isJWK before you trust them.

isJWKSet({ keys: [esPublic] }); // true
isJWKSet({ keys: [] }); // true
isJWKSet(esPublic); // false

isSymmetricJWK #

True for oct keys that carry k. Their k is the shared secret, so a symmetric JWK is always secret.

isSymmetricJWK(hs); // true
isSymmetricJWK(esPrivate); // false
isSymmetricJWK({ kty: "oct" }); // false

isAsymmetricJWK #

True for an RSA, EC or OKP JWK, public or private. Anything that is not a JWK, and every oct key, is false.

isAsymmetricJWK(esPublic); // true
isAsymmetricJWK(esPrivate); // true
isAsymmetricJWK(hs); // false
isAsymmetricJWK("x"); // false

isPrivateJWK #

True for an RSA, EC or OKP key with a d member. It returns false for every oct key, even though an oct key is secret.

isPrivateJWK(esPrivate); // true
isPrivateJWK(esPublic); // false
isPrivateJWK(hs); // false

::warning !isPrivateJWK(key) is no "safe to publish" check, because it lets every oct key through. Use isPublicJWK to decide what goes into a JWKS endpoint. ::

isPublicJWK #

The "safe to publish" check. True only for an asymmetric key that has no d or k and carries the public fields its type needs: x and y for EC, x for OKP, n and e for RSA.

isPublicJWK(esPublic); // true
isPublicJWK(esPrivate); // false
isPublicJWK(hs); // false
isPublicJWK({ kty: "EC", crv: "P-256", x: "abc" }); // false, no y

isCryptoKey and assertCryptoKey #

isCryptoKey reads Symbol.toStringTag, so it also recognises a CryptoKey created in another realm, such as a worker or an iframe. assertCryptoKey throws instead of returning false.

isCryptoKey(cryptoKey); // true
isCryptoKey(hs); // false

assertCryptoKey(cryptoKey); // passes
assertCryptoKey(hs);
// JWTError ERR_JWK_INVALID: CryptoKey instance expected

isCryptoKeyPair #

True when both halves are present and both are CryptoKeys.

isCryptoKeyPair(pair); // true
isCryptoKeyPair({ publicKey: pair.publicKey }); // false

isJWTContent #

Reports what a header claims the content is: true when typ contains jwt (case-insensitive) or cty is json, application/json or ends in +json. A non-string typ or cty returns false.

isJWTContent({ typ: "JWT" }); // true
isJWTContent({ typ: "at+jwt" }); // true
isJWTContent({ cty: "application/vnd.example+json" }); // true
isJWTContent({ typ: "text", cty: "text/plain" }); // false
isJWTContent(undefined); // false

::note typ and cty are chosen by whoever made the token. verify() and decrypt() never read them to decide how to parse a payload; you declare that with the payload option. Treat isJWTContent as information, not as a security check. ::

isJWTError #

Narrows to JWTError. With a code, it also checks the code, and a misspelled code fails to type-check. For ERR_JWT_EXPIRED it also types cause as { jti?, iat?, exp? }. It is exported from unjwt, unjwt/jws, unjwt/jwe and unjwt/jwk, not from unjwt/utils.

import { isJWTError, verify } from "unjwt/jws";

const error = await verify("not.a.token", hs).catch((e) => e);
// JWTError ERR_JWS_INVALID: Invalid JWS: Protected header could not be decoded.

isJWTError(error); // true
isJWTError(error, "ERR_JWS_INVALID"); // true
isJWTError(error, "ERR_JWT_EXPIRED"); // false
isJWTError(new Error("x")); // false

isUnsecureError #

Narrows to the UnsecureError class that unjwt bundles. The encoding helpers throw it directly, and a JWTError from a token that did not decode carries it as cause. Import the guard from unjwt: a class from your own unsecure install is a different class.

import { base64UrlParse, isUnsecureError } from "unjwt/utils";

isUnsecureError(error.cause, "MALFORMED"); // true, the error from above
isUnsecureError(error); // false, it is a JWTError

try {
  base64UrlParse("a=");
} catch (e) {
  isUnsecureError(e, "MALFORMED"); // true
}

See also #