basecamp/fizzy · error · ActionPack::WebAuthn::InvalidResponseError
Invalid base64 encoding in authenticator data
Error message
Invalid base64 encoding in authenticator data
What it means
ActionPack::WebAuthn::Authenticator::Data.wrap accepts an existing Data object, a Base64URL string, or raw binary. Non-binary strings go through Base64.urlsafe_decode64, which raises ArgumentError when the input contains characters outside the base64url alphabet (A–Z, a–z, 0–9, -, _); the library re-raises it as InvalidResponseError. It means the authenticator data string was corrupted, double-encoded, or in the wrong base64 variant before parsing.
Source
Thrown at lib/action_pack/web_authn/authenticator/data.rb:73
USER_VERIFIED_FLAG = 0x04
BACKUP_ELIGIBLE_FLAG = 0x08
BACKUP_STATE_FLAG = 0x10
ATTESTED_CREDENTIAL_DATA_FLAG = 0x40
attr_reader :bytes, :relying_party_id_hash, :flags, :sign_count, :aaguid, :credential_id, :public_key_bytes
class << self
# Wraps raw authenticator data into a Data instance. Accepts an existing
# Data object (returned as-is), a Base64URL-encoded string, or raw binary.
def wrap(data)
if data.is_a?(self)
data
else
data = Base64.urlsafe_decode64(data) unless data.encoding == Encoding::BINARY
decode(data)
end
rescue ArgumentError
raise ActionPack::WebAuthn::InvalidResponseError, "Invalid base64 encoding in authenticator data"
end
# Decodes raw authenticator data bytes into a Data instance, parsing the
# RP ID hash, flags, sign count, and (if present) attested credential data.
def decode(bytes)
bytes = bytes.bytes if bytes.is_a?(String)
minimum_length = RELYING_PARTY_ID_HASH_LENGTH + FLAGS_LENGTH + SIGN_COUNT_LENGTH
if bytes.length < minimum_length
raise ActionPack::WebAuthn::InvalidResponseError, "Authenticator data is too short"
end
position = 0
relying_party_id_hash = bytes[position, RELYING_PARTY_ID_HASH_LENGTH].pack("C*")
position += RELYING_PARTY_ID_HASH_LENGTH
flags = bytes[position]View on GitHub (pinned to 7aabe74580)
Solutions
- Encode on the client as Base64URL without padding (e.g. Ruby Base64.urlsafe_encode64(..., padding: false), JS base64url helpers) using the raw bytes from response.authenticatorData / response.getAuthenticatorData().
- Reproduce in console: Base64.urlsafe_decode64(param) must succeed — if it raises ArgumentError the value is mangled in transit, not in the library.
- Check for double URL-encoding or HTML-escaping of the parameter (look for %2B, %2F, " in the raw request body).
- Add a cheap format pre-check on the boundary and reject with 400 so the error surfaces at the edge.
Example fix
# before
data = ActionPack::WebAuthn::Authenticator::Data.wrap(params[:authenticator_data])
# after — validate format first, then wrap
raw = params[:authenticator_data].to_s
unless raw.match?(%r{\A[A-Za-z0-9_-]+={0,2}\z})
return render json: { error: "authenticator_data must be base64url" }, status: :bad_request
end
data = ActionPack::WebAuthn::Authenticator::Data.wrap(raw) Defensive patterns
Strategy: validation
Validate before calling
BASE64URL_RE = /\A[A-Za-z0-9_-]+={0,2}\z/
return render(json: { error: 'authenticator_data must be base64url' }, status: :bad_request) unless params[:authenticator_data].to_s.match?(BASE64URL_RE) Type guard
def base64url?(value)
value.is_a?(String) && value.match?(%r{\A[A-Za-z0-9_-]+={0,2}\z})
end Try / catch
begin
data = ActionPack::WebAuthn::Authenticator::Data.wrap(raw)
rescue ActionPack::WebAuthn::InvalidResponseError => e
render json: { error: e.message }, status: :bad_request
end Prevention
- Encode all WebAuthn binary fields as unpadded base64url on the client, consistently.
- Never route binary fields through URL form-encoding without normalization of + and /.
- Pre-check format at the request boundary and reject early with a field-specific message.
When it happens
Trigger: Calling Authenticator::Data.wrap (directly or via AttestationResponse/AssertionResponse with an authenticator_data param) with standard Base64 containing + or /, a value mangled by URL form-encoding, a double-encoded string, or plain garbage such as an error message body.
Common situations: JavaScript clients that use btoa() (standard base64) instead of base64url; params stripped or truncated by proxies; test fixtures hand-typed with wrong characters; passing clientDataJSON (JSON text) into a field the server expects to be base64url authenticator data.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Authenticator data is too short
- Client data is not valid JSON
- Unsupported COSE key type/algorithm: #{key_type}/#{algorithm
- Invalid EC2 coordinate length
- Authenticator data is too short for attested credential data
AI-assisted analysis of basecamp/fizzy@7aabe74580 (2026-08-21).
Data as JSON: /api/errors/a3e6a8d3d07b9ea6.
Report an issue: GitHub.