Tencent/WeKnora · error
decode oidc state payload: %w
Error message
decode oidc state payload: %w
What it means
VerifyOIDCState could not base64url-decode the payload segment of the signed OIDC state token. The state string must be exactly '<base64url(payload)>.<base64url(sig))>'; if the payload half is not valid base64 (Raw URL encoding, no padding), decoding fails and the error wraps the underlying reason. This is a malformed/corrupt state, not a signature failure.
Source
Thrown at internal/utils/oidc_state.go:80
if err != nil {
return "", fmt.Errorf("marshal oidc state: %w", err)
}
mac := hmac.New(sha256.New, []byte(oidcStateSigningKey()))
mac.Write(raw)
sig := mac.Sum(nil)
return base64.RawURLEncoding.EncodeToString(raw) + "." + base64.RawURLEncoding.EncodeToString(sig), nil
}
// VerifyOIDCState validates the HMAC and freshness of a state token.
func VerifyOIDCState(raw string) (*OIDCStatePayload, error) {
raw = strings.TrimSpace(raw)
parts := strings.Split(raw, ".")
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 {View on GitHub (pinned to 988cbb0330)
Solutions
- Ensure the state string is passed through URL round-trips unchanged (use URL-safe encoding end-to-end; don't re-encode the already base64url token)
- Regenerate the state with SignOIDCState and confirm it matches the pattern of two dot-separated base64url segments
- Check that cookies/redirects aren't truncating the value (cookie size limits, logging that clips)
- Inspect the wrapped error from base64.RawURLEncoding to identify the exact offending character or length (must be a multiple of 4, unpadded)
Example fix
// before: standard base64 with padding leaks '=' into the state state := base64.StdEncoding.EncodeToString(payload) + "." + base64.StdEncoding.EncodeToString(sig) // after state := base64.RawURLEncoding.EncodeToString(payload) + "." + base64.RawURLEncoding.EncodeToString(sig)
Defensive patterns
Strategy: try-catch
Validate before calling
func stateLooksWellFormed(state string) bool {
parts := strings.Split(state, ".")
if len(parts) != 2 { return false }
for _, p := range parts {
if p == "" { return false }
if _, err := base64.RawURLEncoding.DecodeString(p); err != nil { return false }
}
return true
} Type guard
func isValidOIDCStateShape(s string) bool {
parts := strings.Split(s, ".")
return len(parts) == 2 &&
base64.RawURLEncoding.EncodeToString(mustDecode(parts[0])) == parts[0]
} Try / catch
payload, err := utils.VerifyOIDCState(rawState)
if err != nil {
if strings.Contains(err.Error(), "decode oidc state payload") {
// malformed state: restart the OIDC flow with a fresh state
http.Redirect(w, r, startOIDCFlow(), http.StatusFound)
return
}
http.Error(w, "invalid state", http.StatusBadRequest)
} Prevention
- Always generate states with SignOIDCState; never assemble them by hand
- Round-trip the state opaquely — no re-encoding, HTML escaping, or truncation
- Watch cookie/redirect size limits when the payload embeds long redirect URIs
When it happens
Trigger: Calling VerifyOIDCState with a state value whose first dot-separated segment contains invalid base64url characters (e.g. '+' or '/' from standard base64, '=' padding, or truncation by a URL length limit or middleware).
Common situations: State token truncated when stored in a cookie or query parameter; double URL-encoding/decoding mangling '-' and '_'; a client constructing its own state instead of using SignOIDCState; proxy header size limits clipping the token.
Related errors
- decode oidc state signature: %w
- unmarshal oidc state: %w
- OIDC provider returned no user claims
- empty JWK modulus or exponent
- invalid JWK exponent value
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/b53fa7ec44f7d68f.
Report an issue: GitHub.