{"record":{"id":"75f4c640afe67238","repo":"Tencent/WeKnora","slug":"unmarshal-oidc-state-w","errorCode":null,"errorMessage":"unmarshal oidc state: %w","messagePattern":"unmarshal oidc state: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/utils/oidc_state.go","lineNumber":93,"sourceCode":"\tif len(parts) != 2 {\n\t\treturn nil, errors.New(\"invalid oidc state format\")\n\t}\n\tpayloadBytes, err := base64.RawURLEncoding.DecodeString(parts[0])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode oidc state payload: %w\", err)\n\t}\n\tsigBytes, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode oidc state signature: %w\", err)\n\t}\n\tmac := hmac.New(sha256.New, []byte(oidcStateSigningKey()))\n\tmac.Write(payloadBytes)\n\tif !hmac.Equal(mac.Sum(nil), sigBytes) {\n\t\treturn nil, errors.New(\"oidc state signature mismatch\")\n\t}\n\tvar payload OIDCStatePayload\n\tif err := json.Unmarshal(payloadBytes, &payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"unmarshal oidc state: %w\", err)\n\t}\n\tif strings.TrimSpace(payload.RedirectURI) == \"\" {\n\t\treturn nil, errors.New(\"state.redirect_uri is required\")\n\t}\n\tif payload.IssuedAt == 0 {\n\t\treturn nil, errors.New(\"state.iat is required\")\n\t}\n\tissuedAt := time.Unix(payload.IssuedAt, 0)\n\tif time.Since(issuedAt) > oidcStateMaxAge || time.Until(issuedAt) > time.Minute {\n\t\treturn nil, errors.New(\"oidc state expired or invalid timestamp\")\n\t}\n\treturn &payload, nil\n}\n","sourceCodeStart":75,"sourceCodeEnd":107,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/utils/oidc_state.go#L75-L107","documentation":"The state token's payload decoded and its HMAC verified, but the JSON inside is not a valid OIDCStatePayload. json.Unmarshal failed, meaning the signed bytes are not the expected JSON object with redirect_uri and iat fields. Because the signature matched, this usually indicates a schema/version change in what was signed rather than an attack.","triggerScenarios":"VerifyOIDCState receiving a state signed by a different code version whose payload layout differs (e.g. signed a raw string or a different struct); an old deployment signing while a new one verifies during rolling deploys; key rotation reusing an unrelated blob as payload.","commonSituations":"Rolling deployments with divergent OIDCStatePayload schemas; manually crafted states in tests signed with the right key but wrong JSON shape; migrating state signing between libraries without keeping field names/types (iat as number vs string) stable.","solutions":["Ensure the same OIDCStatePayload struct/field names are used by SignOIDCState and all verifying instances (align deployments before rolling out schema changes)","Check json tags on OIDCStatePayload; iat must be a number and redirect_uri a string in the signed JSON","Clear in-flight states (old cookies) after a schema change so users get a fresh sign→verify cycle","Inspect the wrapped UnmarshalTypeError/ SyntaxError to see which field or syntax failed"],"exampleFix":"// before: signing a plain string\nmac.Write([]byte(redirectURI))\n// after: sign the structured payload\npayload, _ := json.Marshal(OIDCStatePayload{RedirectURI: redirectURI, IssuedAt: time.Now().Unix()})\nmac.Write(payload)","handlingStrategy":"try-catch","validationCode":"func payloadLooksLikeJSON(state string) bool {\n    parts := strings.Split(state, \".\")\n    if len(parts) != 2 { return false }\n    b, err := base64.RawURLEncoding.DecodeString(parts[0])\n    if err != nil { return false }\n    var probe map[string]json.RawMessage\n    return json.Unmarshal(b, &probe) == nil && probe[\"redirect_uri\"] != nil && probe[\"iat\"] != nil\n}","typeGuard":"func isCompatibleOIDCState(b []byte) bool {\n    var p struct {\n        RedirectURI string `json:\"redirect_uri\"`\n        IssuedAt    int64  `json:\"iat\"`\n    }\n    return json.Unmarshal(b, &p) == nil && p.RedirectURI != \"\" && p.IssuedAt > 0\n}","tryCatchPattern":"payload, err := utils.VerifyOIDCState(rawState)\nif err != nil {\n    if strings.Contains(err.Error(), \"unmarshal oidc state\") {\n        // schema drift (rolling deploy / old cookie): issue a fresh state\n        http.Redirect(w, r, startOIDCFlow(), http.StatusFound)\n        return\n    }\n    http.Error(w, \"invalid state\", http.StatusBadRequest)\n}","preventionTips":["Keep OIDCStatePayload's JSON field names stable across releases; add fields, never rename","Coordinate sign/verify schema changes across all replicas during rolling deploys","Version the payload (add a v field) so future format changes are detectable before unmarshal"],"tags":["oidc","json","state-token","schema-mismatch"],"backgroundTag":"oidc-state-verification-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}