FUNCTIONS / @SPFN/SIGNING — TOKEN SIGNING OVER LOCAL KEYS AND KMS

One signer.
Three key homes.

@spfn/signing is the Signer interface and the providers behind it. The private key may live in this process, in Google Cloud KMS, or in AWS KMS; the caller's code does not change, and the token on the wire does not change. It signs EdDSA and ES256, and verifies those two and RS256. Alpha — the API may move more than the beta packages.

Read the reference Get started $ pnpm add @spfn/signing
01
The premise

The key moves.
The code doesn't.

Signing code usually assumes the private key is a string sitting in the process. That assumption does not survive a cloud KMS, where the key never leaves the service. createSigner() takes a provider — local, gcp-kms, or aws-kms — and returns the same Signer either way. EdDSA (Ed25519) is the default; ES256 stays fully supported for anything downstream that only speaks ECDSA. The KMS SDKs are optional peer dependencies, installed only for the provider you name.

createSigner({ provider })
local · gcp-kms · aws-kms
signer.sign(claims)
compact JWS
FIG 01 — ONE INTERFACE · THREE PROVIDERS · EDDSA BY DEFAULT, ES256 SUPPORTED
02
Core technique

The algorithm comes
from the key.

Verification lives in its own entry point, @spfn/signing/verify, a single file whose only import is node:crypto — so it can run inside a tenant container with nothing else shipped beside it. verifyJws() never throws on token input: a malformed token is a verdict, not an exception. The token's kid selects the key, and that key's algorithm is the algorithm; the header's alg is only checked for equality with it. That is what turns algorithm confusion, alg: "none" included, into an alg-mismatch verdict instead of a decision.

token
kid → key
alg must equal the key's
verdict, never a throw
FIG 02 — VERIFY IMPORTS ONLY NODE:CRYPTO · KID PICKS THE KEY · HEADER ALG IS CHECKED, NOT TRUSTED
03
What's inside

Sign, verify,
and rotate.

Keep the key where it belongs.