Note: You are viewing the README for the development version of webauthn-ruby. For the current release version see https://github.com/cedarcode/webauthn-ruby/blob/2-stable/README.md.
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
Please report security vulnerabilities to security@cedarcode.com.
More: SECURITY
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."
# Optionally configure a client timeout hint, in milliseconds.
# This hint specifies how long the browser should wait for any
# interaction with the user.
# This hint may be overridden by the browser.
# https://www.w3.org/TR/webauthn/#dom-publickeycredentialcreationoptions-timeout
# config.credential_options_timeout = 120_000
# 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"
# Configure preferred binary-to-text encoding scheme. This should match the encoding scheme
# used in your client-side (user agent) code before sending the credential to the server.
# Supported values: `:base64url` (default), `:base64` or `false` to disable all encoding.
#
# config.encoding = :base64url
# Possible values: "ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512", "RS1"
# Default: ["ES256", "PS256", "RS256"]
#
# config.algorithms << "ES384"
end
The ceremony where a user, a Relying Party, and the user’s client (containing at least one authenticator) work in concert to create a public key credential and associate it with the user’s Relying Party account. Note that this includes employing a test of user presence or user verification. [source]
# Generate and store the WebAuthn User ID the first time the user registers a credential
if !user.webauthn_id
user.update!(webauthn_id: WebAuthn.generate_user_id)
end
options = WebAuthn::Credential.options_for_create(
user: { id: user.webauthn_id, name: user.name }
exclude: user.credentials.map { |c| c.webauthn_id }
)
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
session[:creation_challenge] = options.challenge
# Send `options` back to the browser, so that they can be used
# to call `navigator.credentials.create({ "publicKey": options })`
#
# You can call `options.as_json` to get a ruby hash with a JSON representation if needed.
# If inside a Rails controller, `render json: options` will just work.
# I.e. it will encode and convert the options to JSON automatically.
# For your frontend code, you might find @github/webauthn-json npm package useful.
# Especially for handling the necessary decoding of the options, and sending the
# `PublicKeyCredential` object back to the server.
# Assuming you're using @github/webauthn-json package to send the `PublicKeyCredential` object back
# in params[:publicKeyCredential]:
webauthn_credential = WebAuthn::Credential.from_create(params[:publicKeyCredential])
begin
webauthn_credential.verify(session[:creation_challenge])
# Store Credential ID, Credential Public Key and Sign Count for future authentications
user.credentials.create!(
webauthn_id: webauthn_credential.id,
public_key: webauthn_credential.public_key,
sign_count: webauthn_credential.sign_count
)
rescue WebAuthn::Error => e
# Handle error
end
The ceremony where a user, and the user’s client (containing at least one authenticator) work in concert to cryptographically prove to a Relying Party that the user controls the credential private key associated with a previously-registered public key credential (see Registration). Note that this includes a test of user presence or user verification. [source]
options = WebAuthn::Credential.options_for_get(allow: user.credentials.map { |c| c.webauthn_id })
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
session[:authentication_challenge] = options.challenge
# Send `options` back to the browser, so that they can be used
# to call `navigator.credentials.get({ "publicKey": options })`
# You can call `options.as_json` to get a ruby hash with a JSON representation if needed.
# If inside a Rails controller, `render json: options` will just work.
# I.e. it will encode and convert the options to JSON automatically.
# For your frontend code, you might find @github/webauthn-json npm package useful.
# Especially for handling the necessary decoding of the options, and sending the
# `PublicKeyCredential` object back to the server.
You need to look up the stored credential for a user by matching the id
attribute from the PublicKeyCredential
interface returned by the browser to the stored credential_id
. The corresponding public_key
and sign_count
attributes must be passed as keyword arguments to the verify
method call.
# Assuming you're using @github/webauthn-json package to send the `PublicKeyCredential` object back
# in params[:publicKeyCredential]:
webauthn_credential = WebAuthn::Credential.from_get(params[:publicKeyCredential])
stored_credential = user.credentials.find_by(webauthn_id: webauthn_credential.id)
begin
webauthn_credential.verify(
session[:authentication_challenge],
public_key: stored_credential.public_key,
sign_count: stored_credential.sign_count
)
# Update the stored credential sign count with the value from `webauthn_credential.sign_count`
stored_credential.update!(sign_count: webauthn_credential.sign_count)
# Continue with successful sign in or 2FA verification...
rescue WebAuthn::SignCountVerificationError => e
# Cryptographic verification of the authenticator data succeeded, but the signature counter was less then or equal
# to the stored value. This can have several reasons and depending on your risk tolerance you can choose to fail or
# pass authentication. For more information see https://www.w3.org/TR/webauthn/#sign-counter
rescue WebAuthn::Error => e
# Handle error
end
Generates a WebAuthn User Handle that follows the WebAuthn spec recommendations.
WebAuthn.generate_user_id # "lWoMZTGf_ml2RoY5qPwbwrkxrvTqWjGOxEoYBgxft3zG-LlrICvE-y8bxFi06zMyIOyNsJoWx4Fa2TOqoRmnxA"
Helper method to build the necessary PublicKeyCredentialCreationOptions
to be used in the client-side code to call navigator.credentials.create({ "publicKey": publicKeyCredentialCreationOptions })
.
creation_options = WebAuthn::Credential.options_for_create(
user: { id: user.webauthn_id, name: user.name }
exclude: user.credentials.map { |c| c.webauthn_id }
)
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
session[:creation_challenge] = creation_options.challenge
# Send `creation_options` back to the browser, so that they can be used
# to call `navigator.credentials.create({ "publicKey": creationOptions })`
#
# You can call `creation_options.as_json` to get a ruby hash with a JSON representation if needed.
# If inside a Rails controller, `render json: creation_options` will just work.
# I.e. it will encode and convert the options to JSON automatically.
Helper method to build the necessary PublicKeyCredentialRequestOptions
to be used in the client-side code to call navigator.credentials.get({ "publicKey": publicKeyCredentialRequestOptions })
.
request_options = WebAuthn::Credential.options_for_get(allow: user.credentials.map { |c| c.webauthn_id })
# Store the newly generated challenge somewhere so you can have it
# for the verification phase.
session[:authentication_challenge] = request_options.challenge
# Send `request_options` back to the browser, so that they can be used
# to call `navigator.credentials.get({ "publicKey": requestOptions })`
# You can call `request_options.as_json` to get a ruby hash with a JSON representation if needed.
# If inside a Rails controller, `render json: request_options` will just work.
# I.e. it will encode and convert the options to JSON automatically.
credential_with_attestation = WebAuthn::Credential.from_create(params[:publicKeyCredential])
credential_with_assertion = WebAuthn::Credential.from_get(params[:publicKeyCredential])
Verifies the created WebAuthn credential is valid.
credential_with_attestation.verify(session[:creation_challenge])
Verifies the asserted WebAuthn credential is valid.
Mainly, that the client provided a valid cryptographic signature for the corresponding stored credential public key, among other extra validations.
credential_with_assertion.verify(
session[:authentication_challenge],
public_key: stored_credential.public_key,
sign_count: stored_credential.sign_count
)
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 |
You can define what trust policy to enforce by setting acceptable_attestation_types
config to a subset of ['None', 'Self', 'Basic', 'AttCA', 'Basic_or_AttCA']
and attestation_root_certificates_finders
to an object that responds to #find
and returns the corresponding root certificate for each registration. The #find
method will be called passing keyword arguments attesation_format
, aaguid
and attestation_certificate_key_id
.
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.