we-promise/sure · error · ActionController::BadRequest

invalid credential payload

Error message

invalid credential payload

What it means

This is the companion guard to "credential must be an object" in webauthn_credential_payload. When the credential param arrives as a String, the helper runs JSON.parse on it; if parsing blows up it is rescued (JSON::ParserError, TypeError, ArgumentError) and re-raised as ActionController::BadRequest (HTTP 400) with "invalid credential payload". It fires specifically when the string is present and shaped like a string but is not syntactically valid JSON — malformed structure, not wrong shape after parsing.

Source

Thrown at app/controllers/concerns/webauthn_relying_party.rb:29

        name: "Sure",
        id: webauthn_config.rp_id,
        allowed_origins: webauthn_config.allowed_origins,
        # Accept consumer passkeys/security keys without attesting device vendor
        # identity; this keeps MFA registration broad for self-hosted users.
        verify_attestation_statement: false
      )
    end

    def webauthn_credential_payload
      payload = params.require(:credential)
      payload = JSON.parse(payload) if payload.is_a?(String)

      payload = payload.to_unsafe_h if payload.respond_to?(:to_unsafe_h)
      raise ActionController::BadRequest, "credential must be an object" unless payload.is_a?(Hash)

      payload
    rescue JSON::ParserError, TypeError, ArgumentError
      raise ActionController::BadRequest, "invalid credential payload"
    end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Validate the string client-side before sending: JSON.parse(credentialStr) must succeed and yield an object
  2. Send via fetch with JSON.stringify so escaping is handled by the serializer, never by string concatenation
  3. Check for body size limits / WAF rules if payloads are large passkey responses getting truncated mid-flight
  4. In Ruby clients, generate the payload with JSON.generate rather than heredocs or interpolation

Example fix

# before (broken client)
body = "credential=" + json.to_s.tr("\"", "'") # single-quoted pseudo-JSON
# => JSON::ParserError rescued -> 400 "invalid credential payload"

# after
body = { credential: JSON.generate(json) }.to_json
# server: JSON.parse succeeds -> Hash -> proceeds to WebAuthn verification
Defensive patterns

Strategy: validation

Validate before calling

# Ruby client, before sending
require "json"

def valid_credential_json?(str)
  JSON.parse(str).is_a?(Hash)
rescue JSON::ParserError, TypeError
  false
end

payload = JSON.generate(credential) # serialize, never concatenate strings

Type guard

def valid_credential_json?(str)
  JSON.parse(str).is_a?(Hash)
rescue JSON::ParserError, TypeError
  false
end

Try / catch

rescue ActionController::BadRequest => e
  # 400 with "invalid credential payload": the string was syntactically broken.
  # Re-serialize client-side with JSON.generate and retry once.
  head :bad_request
end

Prevention

When it happens

Trigger: credential param sent as a truncated JSON string (missing closing brace, e.g. from a URL-truncated form value); single quotes instead of double quotes (credential="{'id': 1}"); unescaped newlines/control characters inside the JSON; a client sending url-encoded JSON that got double-escaped (%7B%22id%22…) so JSON.parse sees percent-encoded junk; nil propagated into JSON.parse raises TypeError and lands here too.

Common situations: Hand-rolled mobile clients or curl scripts with shell quoting bugs; proxies/WAFs that rewrite or truncate long request bodies (passkey payloads are large); frontend code that concatenates the credential JSON into a FormData value without encoding; a test that builds the string with string interpolation producing invalid JSON.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/5dd3a67cb29877a6. Report an issue: GitHub.