ory/kratos · error
Private key decoding failed
Error message
Private key decoding failed
What it means
Apple Sign-In uses a private key (client_secret generated as a signed ES256 JWT). newClientSecret parses the configured PEM private key with x509.ParsePKCS8PrivateKey; this error wraps a failure to parse the PKCS#8 DER bytes inside the PEM block. The PEM was decodable but its contents are not a valid PKCS#8 private key.
Solutions
- Re-download the .p8 key from the Apple Developer portal and re-encode it as proper PKCS#8 PEM (openssl pkcs8 -topk8 -nocrypt -in key.pem).
- If configured via environment variable, ensure newlines are preserved (use \n escapes the config loader understands or a file secret).
- Verify with `openssl pkey -in key.pem -noout` that the key parses outside the app.
- Confirm the PEM block type is "PRIVATE KEY" (PKCS#8), not "EC PRIVATE KEY" or "RSA PRIVATE KEY".
Example fix
// before: APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGT..." (literal backslash-n) // after: load the key from a file/secret so real newlines are preserved
Defensive patterns
Strategy: validation
Validate before calling
pemBytes := []byte(privateKey)
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "PRIVATE KEY" { return errors.New("key is not PKCS#8 PEM") }
if _, err := x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { return fmt.Errorf("invalid PKCS#8 key: %w", err) } Type guard
func isPKCS8ECDSAPrivateKey(pemStr string) (*ecdsa.PrivateKey, bool) {
block, _ := pem.Decode([]byte(pemStr))
if block == nil || block.Type != "PRIVATE KEY" { return nil, false }
k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil { return nil, false }
ek, ok := k.(*ecdsa.PrivateKey)
return ek, ok
} Prevention
- Store Apple .p8 keys as file-backed secrets, not inline env vars, to preserve newlines.
- Convert keys to PKCS#8 with `openssl pkcs8 -topk8 -nocrypt` before configuring.
- Validate the key parses with `openssl pkey -noout` at deploy time.
When it happens
Trigger: Calling newClientSecret (via the oauth2 flow) when the apple private_key config value contains a PEM "PRIVATE KEY" block whose DER payload fails x509.ParsePKCS8PrivateKey — e.g. corrupted base64, wrong key format (PKCS#1 / SEC1 instead of PKCS#8), or truncated key.
Common situations: Copying an Apple .p8 key with extra whitespace/newlines mangled during copy-paste, env-var round-tripping stripping newlines (\n not interpreted), or using a key exported in a non-PKCS8 format.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- failed to decode PEM block containing private key
- failed to initialize provider
- provider type is not supported, supported are
- no credentials found
- Issuer URL must be set to autodiscover PKCE support
AI-assisted analysis of ory/kratos@b86338da04 (2026-09-07).
Data as JSON: /api/errors/eeb85f64140a4ed8.
Report an issue: GitHub.
Appendix: source
Thrown at selfservice/strategy/oidc/provider_apple.go:53
return &ProviderApple{
ProviderGenericOIDC: &ProviderGenericOIDC{
config: config,
reg: reg,
},
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.PrivateKeyIdView on GitHub (pinned to b86338da04)