webauthn-ruby

repository·master·Indexed 20 days ago

https://github.com/cedarcode/webauthn-ruby

A Ruby server library that implements the WebAuthn Relying Party protocol, enabling Ruby and Rails applications to support secure, phishing-resistant passkey registration and authentication. It handles server-side operations for credential registration, authentication, and cryptographic checks, providing tools for both global and instance-based configuration for multi-tenant environments.

Tokens
11.6K
Snippets
36
Records
44
Agent score
73%

What's inside webauthn-ruby

  1. Overview of webauthn-ruby

    master

    webauthn-ruby is a server-side library that turns a Ruby or Rails web server into a functional WebAuthn Relying Party.

    It handles the complex server-side operations required for:

    • Registration: Creating new public key credentials (passkeys).
    • Authentication: Verifying existing credentials.
    • Cryptographic Checks: Performing the necessary security validations required by the WebAuthn standard.
  2. Choose between Global and Instance Based Configuration

    master

    Decide your configuration strategy based on your application architecture:

    • Global Configuration: Best if your application is served from a single hostname or if all users authenticate through a single subdomain (e.g., auth.example.com). This is configured via config/initializers/webauthn.rb.
    • Instance Based Configuration: Required for multi-tenant applications or segmented applications where users register/authenticate on different hostnames or require different security settings. This allows you to create on-demand instances of WebAuthn::RelyingParty.

    Note: Both approaches can co-exist. WebAuthn.configuration.relying_party provides the global instance, while WebAuthn::RelyingParty.new creates isolated instances that do not share state.

  3. Set up and run the FIDO2 conformance test server

    master

    The FIDO2 conformance test server is a REST API implementation designed specifically for use with the FIDO2 Conformance Test Tool.

    Warning: This implementation is intended for testing purposes only and is not representative of a production WebAuthn relying party.

    Prerequisites

    1. Obtain the FIDO2 Conformance Test Tool from the FIDO Alliance website.
    2. For Metadata Service Tests: Download the server metadata file and place it in the same directory as server.rb before starting the server.

    Installation and Execution

    Navigate to the conformance directory, install dependencies via Bundler, and start the server:

    cd spec/conformance
    bundle install
    bundle exec ruby server.rb

    Testing

    Configure your FIDO2 Test Tool to point to the following server URL: http://localhost:4567

    cd spec/conformance
    bundle install
    bundle exec ruby server.rb
  4. Perform Credential Authentication with an instance

    master

    When using an instance-based approach, use the relying_party instance for the authentication ceremony.

    1. Initiation Phase: Generate options for the browser to call navigator.credentials.get. 2. Verification Phase: Verify the signature and update the stored sign count.

    # --- Initiation Phase ---
    options = relying_party.options_for_authentication(
      allow: user.credentials.map { |c| c.webauthn_id }
    )
    
    session[:authentication_challenge] = options.challenge
    # Send `options` to the browser
    
    # --- Verification Phase ---
    begin
      webauthn_credential, stored_credential = relying_party.verify_authentication(
        params[:publicKeyCredential],
        session[:authentication_challenge]
      ) do |webauthn_credential|
        # The block must return the stored credential object
        user.credentials.find_by(external_id: webauthn_credential.id)
      end
    
      # Update the stored sign count
      stored_credential.update!(sign_count: webauthn_credential.sign_count)
    
    rescue WebAuthn::SignCountVerificationError => e
      # Handle signature counter mismatch
    rescue WebAuthn::Error => e
      # Handle other WebAuthn errors
    end
  5. Install the webauthn gem

    master

    To use webauthn-ruby in your Ruby or Rails application, add the gem to your Gemfile and run bundle. Alternatively, you can install it directly via the command line.

    Add this to your Gemfile:

    gem 'webauthn'

    Then run:

    bundle

    Direct Installation

    gem install webauthn
  6. Perform Credential Registration (Ceremony)

    master

    Credential registration is a two-phase process: Initiation and Verification.

    1. Initiation Phase

    Generate options for the browser to call navigator.credentials.create(). You must generate a user_handle for the user if they don't have one, and store the generated challenge in the session to verify it later.

    2. Verification Phase

    After the browser returns the PublicKeyCredential (via credential.toJSON()), use WebAuthn::Credential.from_create to parse it. Verify the credential against the stored challenge. If successful, store the id, public_key, and sign_count in your database.

    # --- Initiation --- 
    # 1. Generate user handle if needed
    user.update!(webauthn_user_handle: WebAuthn.generate_user_handle) if !user.webauthn_user_handle
    
    # 2. Generate options
    options = WebAuthn::Credential.options_for_create(
      user: { id: user.webauthn_user_handle, name: user.name },
      exclude: user.webauthn_credentials.map { |c| c.webauthn_id },
      authenticator_selection: {
        resident_key: "discouraged",
        user_verification: "required"
      }
    )
    
    # 3. Store challenge
    session[:creation_challenge] = options.challenge
    
    # --- Verification --- 
    # 1. Parse from params
    webauthn_credential = WebAuthn::Credential.from_create(params[:publicKeyCredential])
    
    # 2. Verify
    begin
      webauthn_credential.verify(session[:creation_challenge], user_verification: true)
      
      # 3. Persist
      user.webauthn_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
  7. Perform Credential Registration with an instance

    master

    When using an instance-based approach, use the relying_party instance instead of the global WebAuthn module for the registration ceremony.

    1. Initiation Phase: Generate options for the browser to call navigator.credentials.create. 2. Verification Phase: Verify the credential sent back by the browser using the stored challenge.

    # --- Initiation Phase ---
    if !user.webauthn_id
      user.update!(webauthn_id: WebAuthn.generate_user_id)
    end
    
    options = relying_party.options_for_registration(
      user: { id: user.webauthn_id, name: user.name },
      exclude: user.credentials.map { |c| c.external_id }
    )
    
    session[:creation_challenge] = options.challenge
    # Send `options` to the browser (e.g., `render json: options` in Rails)
    
    # --- Verification Phase ---
    begin
      webauthn_credential = relying_party.verify_registration(
        params[:publicKeyCredential],
        session[:creation_challenge]
      )
    
      user.credentials.create!(
        external_id: webauthn_credential.id,
        public_key: webauthn_credential.public_key,
        sign_count: webauthn_credential.sign_count
      )
    rescue WebAuthn::Error => e
      # Handle error
    end
  8. Perform Credential Authentication (Ceremony)

    master

    Authentication is a two-phase process: Initiation and Verification.

    1. Initiation Phase

    Generate options for the browser to call navigator.credentials.get(). If performing a known-user login (2FA/reauth), pass the user's existing credential IDs to the allow parameter. Store the challenge in the session.

    2. Verification Phase

    Parse the returned PublicKeyCredential using WebAuthn::Credential.from_get. Look up the user's stored credential by its id. Verify the credential using the stored public_key and sign_count. If successful, update the stored sign_count with the new value from the credential.

    # --- Initiation --- 
    options = WebAuthn::Credential.options_for_get(
      allow: user.webauthn_credentials.map { |c| c.webauthn_id },
      user_verification: "required"
    )
    session[:authentication_challenge] = options.challenge
    
    # --- Verification --- 
    webauthn_credential = WebAuthn::Credential.from_get(params[:publicKeyCredential])
    stored_credential = user.webauthn_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,
        user_verification: true
      )
    
      # Update sign count
      stored_credential.update!(sign_count: webauthn_credential.sign_count)
    rescue WebAuthn::SignCountVerificationError => e
      # Handle counter error
    rescue WebAuthn::Error => e
      # Handle other errors
    end
  9. Authenticate migrated U2F credentials using AppID

    master

    To authenticate credentials that were migrated from U2F, you must use the FIDO AppID extension. This ensures the credentials are scoped to the original AppID rather than just the RP ID.

    1. Server-side Configuration

    Set the legacy_u2f_appid in your WebAuthn configuration. This tells the library to automatically request the appid extension when generating options for the authentication ceremony (options_for_get).

    2. Frontend Implementation

    When calling navigator.credentials.get(), you must check the client extension results. If the authenticator supports the AppID extension, the result will contain { "appid": true }. You must send this result to your backend along with the credential ID and response.

    3. Verification

    When PublicKeyCredentialWithAssertion#verify is called, the library will automatically detect if the appid extension was used. If appid is true, the library will correctly use the hash of the AppID (instead of the RP ID) to verify the assertion.

    # 1. Configure the legacy AppID
    WebAuthn.configure do |config|
      config.legacy_u2f_appid = "https://login.example.com"
    end
    
    # 2. Generate options (appid extension is requested automatically)
    options = WebAuthn::Credential.options_for_get
    // 3. Frontend: Retrieve extension results
    const credential = await navigator.credentials.get({ publicKey: credentialRequestOptions });
    const clientDataJSON = JSON.parse(new TextDecoder().decode(credential.response.clientDataJSON));
    const extensionResults = credential.getClientExtensionResults();
    
    // Send 'extensionResults' (which contains { appid: true }) to your backend
  10. Migrate registered U2F credentials to WebAuthn

    master

    If you are migrating from the u2f gem by Castle to webauthn-ruby, you can use the WebAuthn::U2fMigrator class to convert existing U2F credentials into WebAuthn-compatible credentials. This allows users to continue using their existing security keys without re-registration.

    The U2fMigrator class mimics the interface of WebAuthn::AuthenticatorAttestationResponse. It can be used to perform real-time conversion during authentication or as a background task to backfill your database with the new format.

    Note that migration is a one-way process: credentials registered via WebAuthn cannot be converted back to U2F.

    require "webauthn/u2f_migrator"
    
    # Assuming u2f_registration is an instance of U2F::Registration from the u2f gem
    migrated_credential = WebAuthn::U2fMigrator.new(
      app_id: domain, # e.g., "https://login.example.com"
      certificate: u2f_registration.certificate,
      key_handle: u2f_registration.key_handle,
      public_key: u2f_registration.public_key,
      counter: u2f_registration.counter
    )
    
    # Access the converted WebAuthn credential object
    webauthn_id = migrated_credential.credential.id
    webauthn_public_key = migrated_credential.credential.public_key
    sign_count = migrated_credential.authenticator_data.sign_count
  11. Configure Attestation trust policies

    master

    You can enforce specific trust policies by configuring acceptable_attestation_types and attestation_root_certificates_finders.

    Supported acceptable_attestation_types include:

    • None
    • Self
    • Basic
    • AttCA
    • Basic_or_AttCA

    If using custom root certificates, provide an object to attestation_root_certificates_finders that responds to #find. The #find method receives attestation_format, aaguid, and attestation_certificate_key_id as keyword arguments.