basecamp/fizzy · error · ActionPack::WebAuthn::InvalidResponseError
Client data is not valid JSON
Error message
Client data is not valid JSON
What it means
Authenticator::Response#client_data runs JSON.parse on the clientDataJSON string handed to AttestationResponse/AssertionResponse. JSON::ParserError is re-raised as InvalidResponseError. Note that the browser's clientDataJSON is plain UTF-8 JSON (not base64), so any wrapping or partial decoding of it on the server will produce this error.
Source
Thrown at lib/action_pack/web_authn/authenticator/response.rb:69
end
def validate!
super
rescue ActiveModel::ValidationError
raise ActionPack::WebAuthn::InvalidResponseError, errors.full_messages.join(", ")
end
# Returns the RelyingParty used for RP ID validation.
def relying_party
ActionPack::WebAuthn.relying_party
end
# Parses the client data JSON string into a Hash. Raises
# +InvalidResponseError+ if the JSON is malformed.
def client_data
@client_data ||= JSON.parse(client_data_json)
rescue JSON::ParserError
raise ActionPack::WebAuthn::InvalidResponseError, "Client data is not valid JSON"
end
def authenticator_data
nil
end
private
def challenge_must_be_present
if client_data["challenge"].blank?
errors.add(:base, "Challenge missing")
end
end
def challenge_must_not_be_expired
return if errors.any?
signed_message = Base64.urlsafe_decode64(client_data["challenge"])
View on GitHub (pinned to 7aabe74580)
Solutions
- Pass the browser's response.clientDataJSON string through verbatim — do not base64-decode it server-side.
- If your transport base64-encodes everything, decode that one field first: JSON.parse(Base64.urlsafe_decode64(value)).
- Pre-validate at the boundary with JSON.parse(value) and return 400 early so the failure names the field.
- Log the first 80 chars of the received value — HTML tags or base64 patterns identify the mangling source fast.
Example fix
# before
response = ActionPack::WebAuthn::Authenticator::AssertionResponse.new(
client_data_json: params[:client_data_json] # arrives base64-encoded
)
# after — decode only if your client encoded it, then hand over clean JSON
json = params[:client_data_json]
json = Base64.urlsafe_decode64(json) if json.match?(/^[A-Za-z0-9_-]+={0,2}$/) && !json.strip.start_with?('{')
response = ActionPack::WebAuthn::Authenticator::AssertionResponse.new(client_data_json: json) Defensive patterns
Strategy: validation
Validate before calling
json = params[:client_data_json].to_s
begin
JSON.parse(json)
rescue JSON::ParserError
return render(json: { error: 'client_data_json must be raw JSON' }, status: :bad_request)
end Type guard
def valid_client_data_json?(value) JSON.parse(value.to_s) true rescue JSON::ParserError false end
Try / catch
begin
response = ActionPack::WebAuthn::Authenticator::AssertionResponse.new(client_data_json: json, **rest)
response.validate!
rescue ActionPack::WebAuthn::InvalidResponseError => e
render json: { error: e.message }, status: :bad_request
end Prevention
- Send clientDataJSON as plain UTF-8 JSON; base64-encode only binary fields (authenticatorData, signature).
- If the transport base64-encodes everything, decode that one field before constructing the Response.
- Add a JSON.parse smoke check in request specs for every WebAuthn endpoint.
When it happens
Trigger: Constructing a Response with client_data_json that is base64-encoded JSON, an empty string, an HTML error page captured by a proxy, a truncated body, or a Ruby-inspect artifact like \"{\"type\":...}\" from string interpolation.
Common situations: Teams that uniformly base64-encode all WebAuthn fields client-side and forget to decode clientDataJSON server-side; fetch wrappers that .text() a failed request and pass the error page along; truncation by middleware; test fixtures storing clientDataJSON already-decoded once.
Related errors
- Invalid base64 encoding in authenticator data
- Authenticator data is too short
- Unsupported COSE key type/algorithm: #{key_type}/#{algorithm
- Authenticator data is too short for attested credential data
- Authenticator data is too short for credential ID and public
AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21).
Data as JSON: /api/errors/240b21d249954de3.
Report an issue: GitHub.