Skip to content
ecdsa.com
golang-jwt/jwt (Go)Go

Error message

token signature is invalid: crypto/ecdsa: verification error

Also printed as

  • token signature is invalid: crypto/rsa: verification error
  • token signature is invalid: signature is invalid
  • token signature is invalid: ed25519: verification error
  • crypto/ecdsa: verification error

golang-jwt v4 returns only the inner text, e.g. "crypto/ecdsa: verification error". Use `errors.Is(err, jwt.ErrTokenSignatureInvalid)` (v5) instead of matching strings.

golang-jwt/jwt (Go) — jwt.ErrTokenSignatureInvalid joined with the algorithm's verification error

What it means

golang-jwt parsed the token, accepted its algorithm and obtained a key from your `Keyfunc`, but the signature did not verify with that key. In v5 the error joins `jwt.ErrTokenSignatureInvalid` ("token signature is invalid") with the algorithm's own error: `crypto/ecdsa: verification error` for ES*, `crypto/rsa: verification error` for RS*/PS*, `signature is invalid` for HS*. The token's bytes and the key simply do not belong together.

Why it happens

How to fix it

  1. 1.

    Resolve the key by kid from the JWKS

    Let the Keyfunc pick the key named in the token header from the issuer's key set and refresh it on rotation (for example with `github.com/MicahParks/keyfunc`). Always pin `WithValidMethods`.

    go
    import (
    	"context"
    	"github.com/MicahParks/keyfunc/v3"
    	"github.com/golang-jwt/jwt/v5"
    )
    
    // Resolve the verification key from the issuer's JWKS by the token's kid.
    k, err := keyfunc.NewDefaultCtx(ctx, []string{"https://issuer.example.com/.well-known/jwks.json"})
    if err != nil {
    	return err
    }
    token, err := jwt.Parse(raw, k.Keyfunc,
    	jwt.WithValidMethods([]string{"ES256"}),
    	jwt.WithIssuer("https://issuer.example.com/"),
    	jwt.WithAudience("https://api.example.com"))
  2. 2.

    Prove both sides use the same key

    Hash the DER-encoded public key on the verifier and the public half of the issuer's private key. Different hashes end the investigation.

    bash
    # Same key on both sides? Compare SPKI fingerprints.
    openssl pkey -pubin -in verifier-public.pem -outform DER | sha256sum
    openssl pkey -in issuer-private.pem -pubout -outform DER | sha256sum
  3. 3.

    Handle the error class, not the string

    golang-jwt v5 exposes sentinel errors. Map a signature failure to 401, and do not fall back to trying other keys or skipping verification.

    go
    switch {
    case errors.Is(err, jwt.ErrTokenSignatureInvalid):
    	return http.StatusUnauthorized // wrong key or tampered token: never retry with another key blindly
    case errors.Is(err, jwt.ErrTokenExpired):
    	return http.StatusUnauthorized // client should refresh
    case errors.Is(err, jwt.ErrTokenMalformed):
    	return http.StatusBadRequest
    }

Related errors

Browse the full signature error database