ory/hydra · error

unsupported private key type: %T

Error message

unsupported private key type: %T

What it means

jwt.DefaultSigner.Generate reached the outermost 'default' branch of its key type switch: the private key returned by GetPrivateKey is none of the recognized shapes (ECDSA/RSA/ed25519 key, JSONWebKey, or KeyPair). This is the top-level 'unsupported private key type' variant, distinct from the key-pair variant.

Source

Thrown at fosite/token/jwt/jwt.go:78

		case *rsa.PrivateKey:
			alg := jose.RS256
			if len(t.Algs()) > 0 {
				alg = t.Algs()[0]
			}

			return generateToken(claims, header, alg, t)
		case *ecdsa.PrivateKey:
			alg := jose.ES256
			if len(t.Algs()) > 0 {
				alg = t.Algs()[0]
			}

			return generateToken(claims, header, alg, t)
		default:
			return "", "", errors.Errorf("unsupported private / public key pairs: %T, %T", t, tt)
		}
	default:
		return "", "", errors.Errorf("unsupported private key type: %T", t)
	}
}

// Validate validates a token and returns its signature or an error if the token is not valid.
func (j *DefaultSigner) Validate(ctx context.Context, token string) (string, error) {
	key, err := j.GetPrivateKey(ctx)
	if err != nil {
		return "", err
	}

	if t, ok := key.(*jose.JSONWebKey); ok {
		key = t.Key
	}

	switch t := key.(type) {
	case *rsa.PrivateKey:
		return validateToken(token, t.PublicKey)
	case *ecdsa.PrivateKey:

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Return a concrete supported key type (*rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, or *jose.JSONWebKey wrapping one) from GetPrivateKey
  2. For symmetric needs, use HMAC signing instead of the JWT RS/ES/Ed signers (oct keys are not valid here)
  3. Parse keys with x509/jose before handing them to the signer and add a %T assertion/log
  4. Check that the configured JWK set actually contains signing (asymmetric) keys, not encryption keys

Example fix

// before
func (s *Store) GetPrivateKey(ctx) (interface{}, error) { return s.rawPEM, nil }
// after
func (s *Store) GetPrivateKey(ctx) (interface{}, error) {
    block, _ := pem.Decode(s.rawPEM)
    return x509.ParseECPrivateKey(block.Bytes)
}
Defensive patterns

Strategy: type-guard

Validate before calling

key, err := store.GetPrivateKey(ctx)
if err != nil { return err }
switch key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *jose.JSONWebKey:
    // ok
default:
    return fmt.Errorf("GetPrivateKey returned unsupported type %T", key)
}

Type guard

func isJWTPrivateKey(key interface{}) bool {
    switch key.(type) {
    case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *jose.JSONWebKey:
        return true
    }
    return false
}

Try / catch

token, sig, err := signer.Generate(ctx, claims, header)
if err != nil && strings.Contains(err.Error(), "unsupported private key type") {
    log.Fatalf("configured key %T cannot sign JWTs", key)
}

Prevention

When it happens

Trigger: Calling Generate when GetPrivateKey(ctx) yields something entirely unexpected — e.g. a []byte, pem string, *jose.JSONWebKey whose Key itself is an interface{} the inner switch also rejected, or nil/mis-typed custom signer key.

Common situations: Custom GetPrivateKey implementations returning raw key material instead of parsed Go key types; configuration loading a JWK file where the key type field maps to an unhandled algorithm (e.g. oct keys used with asymmetric signing); refactors that changed the key type passed to NewSigner.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/3d7df2a63a61dca8. Report an issue: GitHub.