golang-jwt error
token signature is invalid: signing method ES256 is invalid
"token signature is invalid: signing method ES256 is invalid" is the golang-jwt v5 error for a token whose alg header is not in the list given to jwt.WithValidMethods. For a Supabase token it usually means the Go service was written for the legacy shared secret, with WithValidMethods([]string{"HS256"}), while the project now signs access tokens with an asymmetric key such as ES256. Supabase publishes the public keys at https://PROJECT-ID.supabase.co/auth/v1/.well-known/jwks.json. Verify with a keyfunc that reads that endpoint, allow the algorithm the key uses, and redeploy. Supabase's JWT docs also describe checking a token with the Auth server instead of verifying it yourself.
Which golang-jwt messages mean what?
Every signature failure starts with "token signature is invalid" (ErrTokenSignatureInvalid). What follows it says why. The lines below are built from the golang-jwt v5.3.1 source (parser.go, hmac.go, ecdsa.go, errors.go), where newError joins its parts with a colon and a space; this page did not run them. A failure inside the keyfunc is a different family, "token is unverifiable".
token signature is invalid: signing method ES256 is invalid
alg is not in WithValidMethods
token signature is invalid: signature is invalid
HS256 token, key is []byte, but the secret does not match
token signature is invalid: key is of invalid type: HMAC verify expects []byte
HS256 token, but the keyfunc returned a string
token signature is invalid: key is of invalid type: ECDSA verify expects *ecdsa.PublicKey
ES256 token, but the keyfunc returned the HS256 secret bytes
token signature is invalid: crypto/ecdsa: verification error
ES256 token, wrong public key
token is unverifiable: error while executing keyfunc: ...
the keyfunc itself returned an errorHow do I verify a Supabase JWT in Go with the JWKS endpoint?
The Supabase JWT docs give the endpoint as GET https://project-id.supabase.co/auth/v1/.well-known/jwks.json and say it returns no keys if the project does not use asymmetric signing keys. The keyfunc package (github.com/MicahParks/keyfunc/v3) provides a jwt.Keyfunc for golang-jwt v5 that loads a JWK Set from a URL and refreshes it. Its README builds one with NewDefaultCtx and passes k.Keyfunc to jwt.Parse. List the algorithms you expect in WithValidMethods: golang-jwt's own comment says to use that option. This snippet follows the README and the library signatures; this page did not compile it.
k, err := keyfunc.NewDefaultCtx(ctx, []string{
"https://PROJECT-ID.supabase.co/auth/v1/.well-known/jwks.json",
})
if err != nil {
log.Fatal(err)
}
token, err := jwt.Parse(tokenString, k.Keyfunc,
jwt.WithValidMethods([]string{"ES256", "RS256"}))Should the Go service still use the legacy HS256 secret?
Supabase marks the legacy shared secret as no longer recommended and available for backward compatibility. The JWT docs say they recommend verifying a user's token with the Auth server, and strongly recommend against verifying with the shared secret. They also warn that revoking the legacy secret means the app no longer trusts any JWT signed with it. If a service must still use it, the keyfunc has to return []byte(secret), not the string. When the project rotates keys, Supabase says the JWKS response is cached by its edge for 10 minutes and client libraries may cache it for 10 more, and to wait at least 20 minutes when adding a standby key or revoking one.
Checklist
How to fix it, then check the fix
- 01Find every place the service parses tokens or reads the secret:
grep -rnE "WithValidMethods|SigningMethodHMAC|jwt\.Parse|JWT_SECRET" --include=*.go . - 02Read the alg and kid of a real access token from your project, on your own machine, to see what Supabase signs with:
echo "$TOKEN" | cut -d. -f1 | tr "_-" "/+" | base64 -d 2>/dev/null - 03In the Supabase dashboard, open the JWT signing keys page of the project and note whether the current key is the legacy secret or an asymmetric key. The JWKS URL returns keys only for asymmetric ones.
- 04Switch the verifier to a JWKS keyfunc and make WithValidMethods match the key type, or return the secret as []byte if you stay on HS256. Keep the project URL, not a secret, in the Vercel variable you build the JWKS URL from.
- 05Redeploy and call a protected route with a fresh access token: it answers 200, not 401. After any key rotation, test again after the 20 minutes Supabase mentions for caches.
Related
Related guides
Sources
Official docs behind this checklist
- Supabase, JWTs
- Supabase, JWT signing keys
- golang-jwt v5, parser.go (signing method check, error wrapping)
- golang-jwt v5, errors.go (messages, newError)
- golang-jwt v5, hmac.go (HMAC key type and mismatch errors)
- golang-jwt v5, ecdsa.go (ECDSA key type and verification errors)
- golang-jwt v5, parser_option.go (WithValidMethods)
- keyfunc v3 README (JWKS URL keyfunc for golang-jwt)
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
I use the right secret and still get a signature error. Why?
Read the text after the colon. "HMAC verify expects []byte" means the keyfunc returned a string, and the fix is []byte(secret). "signature is invalid" means the bytes do not match the secret the project signs with. "signing method ES256 is invalid" means the project no longer signs with HS256.
Is an expired token the same error?
No. golang-jwt reports "token is expired" (ErrTokenExpired) for the exp claim, separate from the signature errors. For the Supabase side of expiry, see the JWT expired page.
Can I skip WithValidMethods?
It works without it, but the golang-jwt source comment for WithValidMethods says it is heavily encouraged, to prevent attacks that abuse the alg header. Pass the algorithms you expect.