Tencent/WeKnora · error
unmarshal oidc state: %w
Error message
unmarshal oidc state: %w
What it means
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.
Source
Thrown at internal/utils/oidc_state.go:93
if len(parts) != 2 {
return nil, errors.New("invalid oidc state format")
}
payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return nil, fmt.Errorf("decode oidc state payload: %w", err)
}
sigBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, fmt.Errorf("decode oidc state signature: %w", err)
}
mac := hmac.New(sha256.New, []byte(oidcStateSigningKey()))
mac.Write(payloadBytes)
if !hmac.Equal(mac.Sum(nil), sigBytes) {
return nil, errors.New("oidc state signature mismatch")
}
var payload OIDCStatePayload
if err := json.Unmarshal(payloadBytes, &payload); err != nil {
return nil, fmt.Errorf("unmarshal oidc state: %w", err)
}
if strings.TrimSpace(payload.RedirectURI) == "" {
return nil, errors.New("state.redirect_uri is required")
}
if payload.IssuedAt == 0 {
return nil, errors.New("state.iat is required")
}
issuedAt := time.Unix(payload.IssuedAt, 0)
if time.Since(issuedAt) > oidcStateMaxAge || time.Until(issuedAt) > time.Minute {
return nil, errors.New("oidc state expired or invalid timestamp")
}
return &payload, nil
}
View on GitHub (pinned to 988cbb0330)
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
Example fix
// before: signing a plain string
mac.Write([]byte(redirectURI))
// after: sign the structured payload
payload, _ := json.Marshal(OIDCStatePayload{RedirectURI: redirectURI, IssuedAt: time.Now().Unix()})
mac.Write(payload) Defensive patterns
Strategy: try-catch
Validate before calling
func payloadLooksLikeJSON(state string) bool {
parts := strings.Split(state, ".")
if len(parts) != 2 { return false }
b, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil { return false }
var probe map[string]json.RawMessage
return json.Unmarshal(b, &probe) == nil && probe["redirect_uri"] != nil && probe["iat"] != nil
} Type guard
func isCompatibleOIDCState(b []byte) bool {
var p struct {
RedirectURI string `json:"redirect_uri"`
IssuedAt int64 `json:"iat"`
}
return json.Unmarshal(b, &p) == nil && p.RedirectURI != "" && p.IssuedAt > 0
} Try / catch
payload, err := utils.VerifyOIDCState(rawState)
if err != nil {
if strings.Contains(err.Error(), "unmarshal oidc state") {
// schema drift (rolling deploy / old cookie): issue a fresh state
http.Redirect(w, r, startOIDCFlow(), http.StatusFound)
return
}
http.Error(w, "invalid state", http.StatusBadRequest)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- decode response: %w
- decode oidc state payload: %w
- decode oidc state signature: %w
- OIDC provider returned no user claims
- empty JWK modulus or exponent
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/75f4c640afe67238.
Report an issue: GitHub.