Error message
secretOrPublicKey must be an asymmetric key when using ES256
What it means
You asked jsonwebtoken to verify (or sign — the twin message says secretOrPrivateKey) an ES256 token, but the key material you provided is a symmetric secret — a plain string or Buffer that is not a PEM/KeyObject. ES256 is ECDSA on P-256: it needs an EC key pair, not a shared secret.
Why it happens
An HMAC-era secret left in the config
commonThe service used HS256 with a shared secret, someone switched alg to ES256, and JWT_SECRET still holds "supersecret". A random string is not an EC key, and jsonwebtoken refuses to pretend it is.
The wrong config variable is wired in
commonThe PEM exists, but the code reads a variable that holds something else — a key ID, a JWKS URL ("https://issuer/.well-known/jwks.json" is a string, not a key), or an empty value in this particular environment.
JWK passed where PEM/KeyObject is expected
occasionalA JWK is a JSON object; stringifying it and handing it to jwt.verify() yields a string that is neither PEM nor KeyObject. It must be converted first (crypto.createPublicKey({ key: jwk, format: "jwk" })).
How to fix it
- 1.
Load a real EC public key
Verify ES256 with a PEM (SPKI) or a KeyObject. If you have a JWK — from a JWKS endpoint, for example — convert it with node:crypto first.
js import { createPublicKey } from "node:crypto"; const key = createPublicKey({ key: jwk, format: "jwk" }); // or a PEM string jwt.verify(token, key, { algorithms: ["ES256"] }); - 2.
Resolve JWKS keys with a helper
If your issuer publishes keys at a JWKS URL, do not pass the URL as the key — resolve it per-token by kid.
js import { createRemoteJWKSet, jwtVerify } from "jose"; const jwks = createRemoteJWKSet(new URL("https://issuer.example/.well-known/jwks.json")); const { payload } = await jwtVerify(token, jwks, { algorithms: ["ES256"] }); - 3.
Or: you actually wanted HS256
If both issuer and verifier are your own service and a shared secret is intentional, set algorithm: "HS256" on both sides instead of ES256. Mixing the two families is what this error prevents — including the classic key-confusion attack scenario.