ory/hydra · error
priv/pub JWK key mismatch
Error message
priv/pub JWK key mismatch
What it means
LoadJSONWebKey (oryx/josex/utils.go:39) parses a JSON Web Key and, after validating it, checks that the key's public/private character matches the `pub` flag the caller passed. `jwk.IsPublic()` reports whether the JWK contains only public members (no "d" private exponent etc.). The error means the caller asked for, say, a public key but the JSON contained private key material — or vice versa.
Source
Thrown at oryx/josex/utils.go:39
"encoding/pem"
"errors"
"fmt"
"github.com/go-jose/go-jose/v3"
)
// LoadJSONWebKey returns a *jose.JSONWebKey for a given JSON string.
func LoadJSONWebKey(json []byte, pub bool) (*jose.JSONWebKey, error) {
var jwk jose.JSONWebKey
err := jwk.UnmarshalJSON(json)
if err != nil {
return nil, err
}
if !jwk.Valid() {
return nil, errors.New("invalid JWK key")
}
if jwk.IsPublic() != pub {
return nil, errors.New("priv/pub JWK key mismatch")
}
return &jwk, nil
}
// LoadPublicKey loads a public key from PEM/DER/JWK-encoded data.
func LoadPublicKey(data []byte) (interface{}, error) {
input := data
block, _ := pem.Decode(data)
if block != nil {
input = block.Bytes
}
// Try to load SubjectPublicKeyInfo
pub, err0 := x509.ParsePKIXPublicKey(input)
if err0 == nil {
return pub, nil
}View on GitHub (pinned to 4174065ffb)
Solutions
- Check which half of the keypair your config expects: for LoadPublicKey/verification use the public JWK; for LoadPrivateKey/signing use the private JWK (one containing "d").
- Inspect the raw JWK JSON: presence of the "d" member means it is private; absence means public.
- Re-export the correct key half from your key management system or with `go-jose` / openssl and update the configuration.
- If you only need the public part and only have the private JWK, derive the public key from the private one (e.g. x509/ECDSA/RSA public of the parsed private key) instead of passing the private JWK to LoadPublicKey.
Example fix
// before: private JWK fed to LoadPublicKey -> "priv/pub JWK key mismatch" pub, err := josex.LoadPublicKey([]byte(privateJWKJSON)) // after: export the public half first var priv jose.JSONWebKey _ = priv.UnmarshalJSON([]byte(privateJWKJSON)) pubJWK := priv.Public() // strips private members pub, err := josex.LoadPublicKey(mustMarshalJSON(pubJWK))
Defensive patterns
Strategy: validation
Validate before calling
func isPublicJWK(jwkJSON []byte) bool {
var raw struct{ D string `json:"d"` }
return json.Unmarshal(jwkJSON, &raw) == nil && raw.D == ""
}
// call LoadPublicKey only when isPublicJWK(data), LoadPrivateKey only when !isPublicJWK(data) Type guard
func assertKeyHalfMatches(jwkJSON []byte, wantPublic bool) error {
var jwk jose.JSONWebKey
if err := jwk.UnmarshalJSON(jwkJSON); err != nil { return err }
if !jwk.Valid() { return errors.New("invalid JWK") }
if jwk.IsPublic() != wantPublic { return errors.New("priv/pub JWK key mismatch") }
return nil
} Prevention
- Name config fields explicitly (public_key_jwk vs private_key_jwk) so operators paste the right half.
- Check for the "d" member in stored JWKs as part of config validation at startup.
- Fail fast at boot with a clear message instead of lazily parsing keys per request.
- Keep public JWKS and private signing keys in separate secret paths.
When it happens
Trigger: Calling LoadJSONWebKey(json, true) with a JWK that contains private components (e.g. RSA "d", EC "d"), or calling LoadJSONWebKey(json, false) with a public-only JWK. Since LoadPublicKey calls LoadJSONWebKey(data, true), feeding a private JWK to LoadPublicKey also triggers it (surfaced wrapped in the 'parse error' message); same for LoadPrivateKey(data) with a public JWK.
Common situations: Config files or environment variables holding a JWK where someone pasted the public half where the private half is expected (e.g. signing key config) or the opposite (verification key config); key-rotation tooling exporting the wrong half of a keypair; copying the JWKS public keys into a private-key setting.
Related errors
- cookiex: at least one secret is required
- unknown algorithm %s for signing key
- unknown algorithm %s for encryption key
- ErrUnsupportedKeyAlgorithm
- unsupported key algorithm: %s
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/e9a6fd4721c74d9e.
Report an issue: GitHub.