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

  1. 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)
  2. Regenerate the state with SignOIDCState and confirm it matches the pattern of two dot-separated base64url segments
  3. Check that cookies/redirects aren't truncating the value (cookie size limits, logging that clips)
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/b53fa7ec44f7d68f. Report an issue: GitHub.