WebAuthn ruby server library
Makes your Ruby/Rails web server become a functional WebAuthn Relying Party.
Takes care of the server-side operations needed to register or authenticate a user credential, including the necessary cryptographic checks.
- Security
- Background
- Prerequisites
- Install
- Usage
- API
- Attestation Statement Formats
- Testing Your Integration
- Contributing
- License
If you have discovered a security bug, please send an email to security@cedarcode.com instead of posting to the GitHub issue tracker.
WebAuthn (Web Authentication) is a W3C standard for secure public-key authentication on the Web supported by all leading browsers and platforms.
- Guide to Web Authentication by Duo
- What is WebAuthn? by Yubico
- WebAuthn W3C Recommendation (i.e. "The Standard")
- Web Authentication API in MDN
- How to use WebAuthn in Android apps
- Security Benefits for WebAuthn Servers (a.k.a Relying Parties)
This ruby library will help your Ruby/Rails server act as a conforming Relying-Party, in WebAuthn terminology. But for the Registration and Authentication ceremonies to fully work, you will also need to add two more pieces to the puzzle, a conforming User Agent + Authenticator pair.
Known conformant pairs are, for example:
- Google Chrome for Android 70+ and Android's Fingerprint-based platform authenticator
- Microsoft Edge and Windows 10 platform authenticator
- Mozilla Firefox for Desktop and Yubico's Security Key roaming authenticator via USB
For a detailed picture about what is conformant and what not, you can refer to:
Add this line to your application's Gemfile:
gem 'webauthn'
And then execute:
$ bundle
Or install it yourself as:
$ gem install webauthn
You can find a working example on how to use this gem in a Rails app in webauthn-rails-demo-app.
If you are migrating an existing application from the legacy FIDO U2F JavaScript API to WebAuthn, also refer to
docs/u2f_migration.md
.
For a Rails application this would go in config/initializers/webauthn.rb
.
WebAuthn.configure do |config|
# This value needs to match `window.location.origin` evaluated by
# the User Agent during registration and authentication ceremonies.
config.origin = "https://auth.example.com"
# Relying Party name for display purposes
config.rp_name = "Example Inc."
# You can optionally specify a different Relying Party ID
# (https://www.w3.org/TR/webauthn/#relying-party-identifier)
# if it differs from the default one.
#
# In this case the default would be "auth.example.com", but you can set it to
# the suffix "example.com"
#
# config.rp_id = "example.com"
end
credential_creation_options = WebAuthn.credential_creation_options
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
#
# You can read it from the resulting options:
credential_creation_options[:challenge]
# Send `credential_creation_options` to the browser, so that they can be used
# to call `navigator.credentials.create({ "publicKey": credentialCreationOptions })`
# These should be ruby `String`s encoded as binary data, e.g. `Encoding:ASCII-8BIT`.
#
# If the user-agent is a web browser, you would use some encoding algorithm to send what
# `navigator.credentials.create` returned through the wire.
#
# Then you need to decode that data before passing it to the `#verify` method.
#
# E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
# on the user-agent encoded data before calling `#verify`
attestation_object = "..."
client_data_json = "..."
attestation_response = WebAuthn::AuthenticatorAttestationResponse.new(
attestation_object: attestation_object,
client_data_json: client_data_json
)
begin
attestation_response.verify(expected_challenge)
# 1. Register the new user and
# 2. Keep Credential ID, Credential Public Key and Sign Count under storage
# for future authentications
# Access by invoking:
# `attestation_response.credential.id`
# `attestation_response.credential.public_key`
# `attestation_response.authenticator_data.sign_count`
rescue WebAuthn::VerificationError => e
# Handle error
end
Assuming you have the previously stored Credential ID, now in variable credential_id
credential_request_options = WebAuthn.credential_request_options
credential_request_options[:allowCredentials] << { id: credential_id, type: "public-key" }
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
#
# You can read it from the resulting options:
credential_request_options[:challenge]
# Send `credential_request_options` to the browser, so that they can be used
# to call `navigator.credentials.get({ "publicKey": credentialRequestOptions })`
Assuming you have the previously stored Credential Public Key, now in variable credential_public_key
# These should be ruby `String`s encoded as binary data, e.g. `Encoding:ASCII-8BIT`.
#
# If the user-agent is a web browser, you would use some encoding algorithm to send what
# `navigator.credentials.get` returned through the wire.
#
# Then you need to decode that data before passing it to the `#verify` method.
#
# E.g. in https://github.com/cedarcode/webauthn-rails-demo-app we use `Base64.strict_decode64`
# on the user-agent encoded data before calling `#verify`
selected_credential_id = "..."
authenticator_data = "..."
client_data_json = "..."
signature = "..."
assertion_response = WebAuthn::AuthenticatorAssertionResponse.new(
credential_id: selected_credential_id,
authenticator_data: authenticator_data,
client_data_json: client_data_json,
signature: signature
)
# This hash must have the id and its corresponding public key of the
# previously stored credential for the user that is attempting to sign in.
allowed_credential = {
id: credential_id,
public_key: credential_public_key,
sign_count: sign_count,
}
begin
assertion_response.verify(expected_challenge, allowed_credentials: [allowed_credential])
# Sign in the user
rescue WebAuthn::VerificationError => e
# Handle error
end
# Find the selected credential in your data storage using `selected_credential_id`
# Update the stored sign count with the value from `assertion_response.authenticator_data.sign_count`
Pending
Attestation Statement Format | Supported? |
---|---|
packed (self attestation) | Yes |
packed (x5c attestation) | Yes |
packed (ECDAA attestation) | No |
tpm (x5c attestation) | Yes |
tpm (ECDAA attestation) | No |
android-key | Yes |
android-safetynet | Yes |
fido-u2f | Yes |
none | Yes |
NOTE: Be aware that it is up to you to do "trust path validation" (steps 15 and 16 in Registering a new credential) if that's a requirement of your Relying Party policy. The gem doesn't perform that validation for you right now.
The Webauthn spec requires for data that is signed and authenticated. As a result, it can be difficult to create valid test authenticator data when testing your integration. webauthn-ruby exposes WebAuthn::FakeClient for you to use in your tests. Example usage can be found in webauthn-ruby/spec/webauthn/authenticator_assertion_response_spec.rb.
Bug reports, feature suggestions, and pull requests are welcome on GitHub at https://github.com/cedarcode/webauthn-ruby.
The library is available as open source under the terms of the MIT License.