{"record":{"id":"9ab698c8d3814b00","repo":"OpenNHP/opennhp","slug":"missing-or-invalid-jwk-in-header","errorCode":null,"errorMessage":"missing or invalid jwk in header","messagePattern":"missing or invalid jwk in header","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/kbs/resource/resource.go","lineNumber":222,"sourceCode":"\t}\n\n\tciphertext = gcm.Seal(nil, iv, plaintext, nil)\n\n\treturn ciphertext, iv, nil, nil\n}\n\nfunc VerifyJWT(tokenString string) (*jwt.Token, error) {\n\t// First parse the token without verification to get the header\n\tparser := jwt.NewParser()\n\tunverifiedToken, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse token: %w\", err)\n\t}\n\n\t// Extract JWK from header\n\tjwkHeader, ok := unverifiedToken.Header[\"jwk\"].(map[string]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing or invalid jwk in header\")\n\t}\n\n\t// Convert JWK back to ECDSA public key\n\txStr, ok := jwkHeader[\"x\"].(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing x coordinate in jwk\")\n\t}\n\tyStr, ok := jwkHeader[\"y\"].(string)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"missing y coordinate in jwk\")\n\t}\n\n\txBytes, err := base64.RawURLEncoding.DecodeString(xStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid x coordinate: %w\", err)\n\t}\n\tyBytes, err := base64.RawURLEncoding.DecodeString(yStr)\n\tif err != nil {","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/kbs/resource/resource.go#L204-L240","documentation":"After successful unverified parsing, VerifyJWT requires the JOSE header to carry a 'jwk' object containing the signer's public key; if the header lacks 'jwk' or it is not a JSON object, it returns 'missing or invalid jwk in header'. This scheme expects embedded-JWK (jwk header parameter) authentication as used by KBS/attestation tokens.","triggerScenarios":"VerifyJWT receives a validly-formed JWT whose header has no 'jwk' field, has jwk as a non-object (string/null/array), or the client signs with a server-side key reference instead of embedding the public key.","commonSituations":"Client library signs tokens without the EmbedJwk option; a generic OIDC/OAuth token (kid-based) is sent where a KBS attestation token is expected; header produced by a different token generator (e.g. plain HS256 token) lacking the jwk parameter.","solutions":["Configure the client signer to embed the public key: e.g. in go-jose use jws.WithEmbeddedKey / the 'jwk' header option when signing.","Verify the client targets the KBS token format (embedded ECDSA P-256 JWK), not a standard OIDC token.","Inspect the token header with `echo <header-b64> | base64 -d` to confirm the jwk field.","Update the client library/version if it drops custom headers during signing.","If tokens come from a different issuer, use the appropriate verification path instead of VerifyJWT."],"exampleFix":"// before: signing without embedded key\ntoken := jwt.NewWithClaims(jwt.SigningMethodES256, claims)\n// after: ensure the public key is embedded in the header\n// (go-jose style)\nsigner, _ := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: priv},\n\t(&jose.SignerOptions{}).EmbedJWK())\njws, _ := signer.Sign(payload)","handlingStrategy":"validation","validationCode":"func headerHasJWK(token string) bool {\n\tparts := strings.Split(token, \".\")\n\tif len(parts) != 3 { return false }\n\thdr, err := base64.RawURLEncoding.DecodeString(parts[0])\n\tif err != nil { return false }\n\tvar h map[string]any\n\tif json.Unmarshal(hdr, &h) != nil { return false }\n\t_, ok := h[\"jwk\"].(map[string]any)\n\treturn ok\n}","typeGuard":null,"tryCatchPattern":"token, err := VerifyJWT(rawToken)\nif err != nil && err.Error() == \"missing or invalid jwk in header\" {\n\t// client did not embed its public key; return 401 with guidance\n}","preventionTips":["Use the signer option that embeds the JWK (e.g. jose EmbedJWK).","Target the KBS attestation token format specifically.","Do not substitute OIDC/OAuth tokens for KBS tokens.","Unit-test token generation to assert the jwk header exists.","Decode and inspect headers in CI when changing signing libraries."],"tags":["jwt","jwk","authentication","header"],"backgroundTag":"missing-required-argument","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}