Skip to content
This repository has been archived by the owner on Aug 2, 2024. It is now read-only.
/ webauthn-ruby Public archive
forked from cedarcode/webauthn-ruby

WebAuthn ruby server library ― Make your Ruby/Rails web server become a conformant WebAuthn Relying Party

License

Notifications You must be signed in to change notification settings

aptible/webauthn-ruby

 
 

Repository files navigation

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

banner

Gem Travis Conventional Commits Join the chat at https://gitter.im/cedarcode/webauthn-ruby

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.

Table of Contents

Security

Please report security vulnerabilities to [email protected].

More: SECURITY

Background

What is WebAuthn?

WebAuthn (Web Authentication) is a W3C standard for secure public-key authentication on the Web supported by all leading browsers and platforms.

Good Intros

In Depth

Prerequisites

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:

Install

Add this line to your application's Gemfile:

gem 'webauthn'

And then execute:

$ bundle

Or install it yourself as:

$ gem install webauthn

Usage

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.

Configuration

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

Credential Registration

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]

Initiation phase

# 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.

Verification phase

# 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

Credential Authentication

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]

Initiation phase

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.

Verification phase

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

API

WebAuthn.generate_user_id

Generates a WebAuthn User Handle that follows the WebAuthn spec recommendations.

WebAuthn.generate_user_id # "lWoMZTGf_ml2RoY5qPwbwrkxrvTqWjGOxEoYBgxft3zG-LlrICvE-y8bxFi06zMyIOyNsJoWx4Fa2TOqoRmnxA"

WebAuthn::Credential.options_for_create(options)

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.

WebAuthn::Credential.options_for_get([options])

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.

WebAuthn::Credential.from_create(credential_create_result)

credential_with_attestation = WebAuthn::Credential.from_create(params[:publicKeyCredential])

WebAuthn::Credential.from_get(credential_get_result)

credential_with_assertion = WebAuthn::Credential.from_get(params[:publicKeyCredential])

PublicKeyCredentialWithAttestation#verify(challenge)

Verifies the created WebAuthn credential is valid.

credential_with_attestation.verify(session[:creation_challenge])

PublicKeyCredentialWithAssertion#verify(challenge, public_key:, sign_count:)

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

Attestation Statement Format

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

Attestation Types

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.

Testing Your Integration

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.

Contributing

See the contributing file!

Bug reports, feature suggestions, and pull requests are welcome on GitHub at https://github.com/cedarcode/webauthn-ruby.

License

The library is available as open source under the terms of the MIT License.

About

WebAuthn ruby server library ― Make your Ruby/Rails web server become a conformant WebAuthn Relying Party

Resources

License

Security policy

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 99.9%
  • Shell 0.1%