ory/kratos · error
Private key is not ecdsa key
Error message
Private key is not ecdsa key
What it means
After PEM decoding, newClientSecret parses the DER bytes with x509.ParsePKCS8PrivateKey and asserts the result is an *ecdsa.PrivateKey, because Apple client-secret JWTs are signed with ES256. If the PKCS8 payload decodes to any other key type (RSA, Ed25519), this error is returned.
Solutions
- Use Apple's ES256 (.p8) ECDSA private key downloaded from the Apple developer portal
- Verify with: openssl pkey -in apple.p8 -text -noout (should say 'Private-Key: (256 bit)' with ASN1 OID prime256v1)
- Generate a fresh .p8 key in Apple Developer console if the current one is not ECDSA
Example fix
// before
"apple": {"private_key": "<rsa pkcs8 pem>"}
// after
"apple": {"private_key": "-----BEGIN PRIVATE KEY-----\n<base64 EC PRIVATE KEY (prime256v1)>\n-----END PRIVATE KEY-----"} Defensive patterns
Strategy: validation
Validate before calling
b, _ := pem.Decode([]byte(privKey)); k, err := x509.ParsePKCS8PrivateKey(b.Bytes); if _, ok := k.(*ecdsa.PrivateKey); !ok { return errors.New("apple key must be ECDSA P-256") } Prevention
- Verify key algorithm with openssl pkey -text before deploying
- Only use Apple .p8 keys for the Apple provider
- Never reuse keys from other OIDC providers
When it happens
Trigger: The Apple provider's private key is a valid PKCS8 PEM block but contains a non-ECDSA key (e.g. an RSA key from another provider), so the type assertion parsedKey.(*ecdsa.PrivateKey) fails.
Common situations: Wrong key pasted from a different OIDC provider; a regenerated Apple key in an unexpected algorithm; test fixtures using RSA keys.
Related errors
- failed to decode PEM block containing private key
- no oidc provider was set
- no audience matched the token's audience
- token audience didn't match allowed audiences: %+v
- Issuer URL must be set to autodiscover PKCE support
AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07).
Data as JSON: /api/errors/7bd994c96bf01f61.
Report an issue: GitHub.
Appendix: source
Thrown at selfservice/strategy/oidc/provider_apple.go:57
},
JWKSUrl: "https://appleid.apple.com/auth/keys",
}
}
func (a *ProviderApple) newClientSecret() (string, error) {
// decode the pem format
block, _ := pem.Decode([]byte(a.config.PrivateKey))
if block == nil || block.Type != "PRIVATE KEY" {
return "", errors.New("failed to decode PEM block containing private key")
}
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", errors.Wrap(err, "Private key decoding failed")
}
privateKey, ok := parsedKey.(*ecdsa.PrivateKey)
if !ok {
return "", errors.New("Private key is not ecdsa key")
}
now := time.Now()
expirationTime := time.Now().Add(5 * time.Minute)
appleToken := jwt.NewWithClaims(jwt.SigningMethodES256,
jwt.RegisteredClaims{
Audience: []string{"https://appleid.apple.com"},
ExpiresAt: jwt.NewNumericDate(expirationTime),
IssuedAt: jwt.NewNumericDate(now),
Issuer: a.config.TeamId,
Subject: a.config.ClientID,
})
appleToken.Header["kid"] = a.config.PrivateKeyId
return appleToken.SignedString(privateKey)
}
View on GitHub (pinned to b86338da04)