Skip to content

Instantly share code, notes, and snippets.

@jacegu
Created July 8, 2024 13:48
Show Gist options
  • Save jacegu/9d1de1c1d7f804917f70f3ee1df30971 to your computer and use it in GitHub Desktop.
Save jacegu/9d1de1c1d7f804917f70f3ee1df30971 to your computer and use it in GitHub Desktop.
Stubbing WebAuthn credential verification in automated tests.
# frozen_string_literal: true
require 'webauthn'
require 'webauthn/fake_client'
# @see https://github.com/cedarcode/webauthn-ruby?tab=readme-ov-file#testing-your-integration
def stub_webauthn_verification
# Generate FakeClient with configured origin.
client = WebAuthn::FakeClient.new(DnsimpleConfig.webauthn_origin)
# Stub challenge. Will be used both for #create and #get.
challenge = Base64.urlsafe_encode64(SecureRandom.bytes(8))
# Create credential with stubbed values
create_result = client.create(challenge:)
# Generate response to be able to extract credential deta relevant for verification.
relying_party = WebAuthn.configuration.relying_party
attestation_object = relying_party.encoder.decode(create_result["response"]["attestationObject"])
client_data_json = relying_party.encoder.decode(create_result["response"]["clientDataJSON"])
response =
WebAuthn::AuthenticatorAttestationResponse
.new(
attestation_object:,
client_data_json:,
relying_party:
)
# Generate payload for WebAuthn::Credential.from_get call.
# There is a temporal coupling here, this needs to happen _after_ the call to #create.
get_result = client.get(challenge:)
{
payload: get_result,
challenge:,
external_id: create_result["id"],
public_key: Base64.urlsafe_encode64(response.credential.public_key),
sign_count: response.authenticator_data.sign_count,
}
end
# You usually retrieve your authentication mean based on the external_id.
# For convenience we will store public key, and sign count there.
FactoryBot.create(:security_key, public_key:, sign_count:)
# Verification goes like:
credential = WebAuthn::Credential.from_get(payload)
credential.verify(challenge, public_key:, sign_count:)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment