{"id":13320446,"url":"https://github.com/alliedcode/passkeys-rails","last_synced_at":"2025-03-11T02:31:47.972Z","repository":{"id":183124605,"uuid":"655174675","full_name":"alliedcode/passkeys-rails","owner":"alliedcode","description":"PasskeysRails - easy to integrate back end for implementing mobile passkeys","archived":false,"fork":false,"pushed_at":"2025-01-13T18:32:51.000Z","size":234,"stargazers_count":20,"open_issues_count":0,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-02-15T05:17:32.910Z","etag":null,"topics":["authentication","passkeys","rails","ruby","webauthn"],"latest_commit_sha":null,"homepage":"https://rubygems.org/gems/passkeys-rails","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alliedcode.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-18T05:45:26.000Z","updated_at":"2025-02-11T12:38:09.000Z","dependencies_parsed_at":"2025-01-13T17:59:39.546Z","dependency_job_id":null,"html_url":"https://github.com/alliedcode/passkeys-rails","commit_stats":null,"previous_names":["alliedcode/passkeys-rails"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alliedcode%2Fpasskeys-rails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alliedcode%2Fpasskeys-rails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alliedcode%2Fpasskeys-rails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alliedcode%2Fpasskeys-rails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alliedcode","download_url":"https://codeload.github.com/alliedcode/passkeys-rails/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242959430,"owners_count":20212978,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","passkeys","rails","ruby","webauthn"],"created_at":"2024-07-29T18:36:08.802Z","updated_at":"2025-03-11T02:31:47.964Z","avatar_url":"https://github.com/alliedcode.png","language":"Ruby","readme":"# PasskeysRails - easy to integrate back end for implementing mobile passkeys\n\n[![Gem Version](https://badge.fury.io/rb/passkeys-rails.svg?cachebust=0.2.1)](https://badge.fury.io/rb/passkeys-rails)\n[![Build Status](https://app.travis-ci.com/alliedcode/passkeys-rails.svg?branch=main)](https://travis-ci.org/alliedcode/passkeys-rails)\n[![codecov](https://codecov.io/gh/alliedcode/passkeys-rails/branch/main/graph/badge.svg?token=UHSNJDUL21)](https://codecov.io/gh/alliedcode/passkeys-rails)\n\n\u003cp align=\"center\" \u003e\nCreated by \u003cb\u003eTroy Anderson, Allied Code\u003c/b\u003e - \u003ca href=\"https://alliedcode.com\"\u003ealliedcode.com\u003c/a\u003e\n\u003c/p\u003e\n\nPasskeysRails is a gem you can add to a Rails app to enable passskey registration and authorization from mobile front ends.  PasskeysRails leverages webauthn for the cryptographic work, and presents a simple API interface for passkey registration, authentication, and testing.\n\nThe purpose of this gem is to make it easy to provide a rails back end API that supports PassKey authentication.  It uses the [`webauthn`](https://github.com/w3c/webauthn) gem to do the cryptographic work and presents a simple API interface for passkey registration and authentication.\n\nThe target use case for this gem is a mobile application that uses a rails based API service to manage resources.  The goal is to make it simple to register and authenticate users using passkeys from mobile applications in a rails API service.\n\nWhat about [devise](https://github.com/heartcombo/devise)?  Devise is awesome, but we don't need all that UI/UX for PassKeys, especially for an API back end.\n\n## Documentation\n* [Usage](#usage)\n* [Installation](#installation)\n* [Rails Integration - Standard](#rails-Integration-standard)\n* [Rails Integration - Grape](#rails-Integration-grape)\n* [Notifications](#notifications)\n* [Failure Codes](#failure-codes)\n* [Testing](#testing)\n* [Mobile App Integration](#mobile-application-integration)\n* [Reference/Example Mobile Applications](#referenceexample-mobile-applications)\n\n## Usage\n\n**PasskeysRails** maintains a `PasskeysRails::Agent` model and related `PasskeysRails::Passkeys`.  In rails apps that maintain their own \"user\" model, add `include PasskeysRails::Authenticatable` to that model and include the name of that class (e.g. `\"User\"`) in the `authenticatable_class` param when calling the register API or set the `PasskeysRails.default_class` to the name of that class.\n\nIn mobile apps, leverage the platform specific Passkeys APIs for ***registration*** and ***authentication***, and call the **PasskeysRails** API endpoints to complete the ceremony.  **PasskeysRails** provides endpoints to support ***registration***, ***authentication***, ***token refresh***, and ***debugging***.\n\n### Optionally providing a **\"user\"** model during registration\n\n**PasskeysRails** does not require any application specific models, but it's often useful to have one.  For example, a User model can be created at registration.  **PasskeysRails** provides two mechanisms to support this.  Either provide the name of the model in the `authenticatable_class` param when calling the `finishRegistration` endpoint, or set a `default_class` in `config/initializers/passkeys_rails.rb`.\n\n**PasskeysRails** supports multiple different application specific models.  Whatever model name supplied when calling the `finishRegistration` endpoint will be created during a successful the `finishRegiration` process. When created, it will be provided an opportunity to do any initialization at that time.\n\nThere are two **PasskeysRails** configuration options related to this: `default_class` and `class_whitelist`:\n\n#### `default_class`\n\nConfigure `default_class` in `config/initializers/passkeys_rails.rb`.  Its value will be used during registration if none is provided in the API call.  The default value is `\"User\"`.  Since the `default_class` is just a default, it can be overridden in the `finishRegiration` API call to use a different model.  If no model is to be used by default, set it to nil.\n\n#### `class_whitelist`\n\nConfigure `class_whitelist` in `config/initializers/passkeys_rails.rb`.  The default value is `nil`.  When `nil`, no whitelist will be applied. If it is non-nil, it should be an array of class names that are allowed during registration.  Supply an empty array to prevent **PasskeysRails** from attempting to create anything other than its own `PasskeysRails::Agent` during registration.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"passkeys-rails\"\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\nOr install it yourself as:\n```bash\n$ gem install passkeys_rails\n```\n\nFinally, execute:\n\n```bash\n$ rails generate passkeys_rails:install\n```\n\nThis will add the `config/initializers/passkeys_rails.rb` configuration file, passkeys routes, and a couple of database migrations to your project.\n\n\n\u003ca id=\"rails-Integration-standard\"\u003e\u003c/a\u003e\n## Rails Integration \u003cp\u003e\u003csmall\u003eAdding to a standard rails project\u003c/small\u003e\u003c/p\u003e\n\n- ### Add `before_action :authenticate_passkey!`\n\nTo prevent access to controller actions, add `before_action :authenticate_passkey!`.  If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.\n\n- ### Use `current_agent` and `current_agent.authenticatable`\n\nTo access the currently authenticated entity, use `current_agent`.  If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`.  For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.\n\n- ### Add `include PasskeysRails::Authenticatable` to model class(es)\n\n If you have one or more classes that you want to use with authentication - e.g. a User class and an AdminUser class - add `include PasskeysRails::Authenticatable` to each of those classes.  That adds a `registered?` method that you can call on your model to determine if they are registerd with your service, and a `registering_with(params)` method that you can override to initialize attributes of your model when it is created during registration. `params` is a hash with params passed to the API when registering.  When called, your object has been built, but not yet saved.  Upon return, **PasskeysRails** will attempt to save your object before finishing registration.  If it is not valid, the registration will fail as well, returning the error error details to the caller.\n\n\u003ca id=\"rails-Integration-grape\"\u003e\u003c/a\u003e\n## Rails Integration - \u003cp\u003e\u003csmall\u003eAdding to a Grape API rails project\u003c/small\u003e\u003c/p\u003e\n\n- ### Call `PasskeysRails.authenticate(request)` to authenticate the request.\n\n Call `PasskeysRails.authenticate(request)` to get an object back that responds to `.success?` and `.failure?` as well as `.agent`, `.code`, and `.message`.\n\n Alternatively, call `PasskeysRails.authenticate!(request)` from a helper in your base class.  It will raise a `PasskeysRails.Error` exception if the caller isn't authenticated.  You can catch the exception and render an appropriate error.  The exception contains the error code and message.\n\n- ### Consider adding the following helpers to your base API class:\n\n```ruby\n  helpers do\n    # Authenticate the request and cache the result\n    def passkey\n      @passkey ||= PasskeysRails.authenticate(request)\n    end\n\n    # Raise an exception if the request is not authentic\n    def authenticate_passkey!\n      error!({ code: passkey.code, message: passkey.message }, :unauthorized) if passkey.failure?\n    end\n\n    # Return the Passkeys::Agent if authentic, else return nil\n    def current_agent\n      passkey.agent\n    end\n\n    # If you have set authenticatable to be a User, you can use this to access the user from Grape endpoint methods\n    def current_user\n      user = current_agent\u0026.authenticatable\n      user.is_a?(User) ? user : nil # sanity check to be sure authenticatable is a User\n    end\n  end\n```\n\n To prevent access to various endpoints, add `before_action :authenticate_passkey!` or call `authenticate_passkey!` from any method that requires authentication.  If an action is attempted without an authenticated entity, an error will be rendered in JSON with an :unauthorized result code.\n\n- ### Use `current_agent` and `current_agent.authenticatable`\n\n To access the currently authenticated entity, use `current_agent`.  If you associated the registration of the agent with one of your own models, use `current_agent.authenticatable`.  For example, if you associated the `User` class with the registration, `current_agent.authenticatable` will be a User object.\n\n## Notifications\n\nCertain actions trigger notifications that can be subscribed.  See `subscribe` in `config/initializers/passkeys_rails.rb`.\n\nThese are completely optional.  **PasskeysRails** will manage all the credentials and keys without these being implemented.  They are useful for taking application specific actions like logging based on the authentication related events.\n\n### Events\n\n- `:did_register ` - a new agent has registered\n\n- `:did_authenticate` - an agent has been authenticated\n\n- `:did_refresh` - an agent's auth token has been refreshed\n\nA convenient place to set these up in is in `config/initializers/passkeys_rails.rb`\n\n```ruby\nPasskeysRails.config do |c|\n  c.subscribe(:did_register) do |event, agent, request|\n    # do something with the agent and/or request\n  end\n\n  c.subscribe(:did_authenticate) do |event, agent, request|\n    # do something with the agent and/or request\n  end\nend\n```\n\nSubscriptions can also be done elsewhere as subscribe is a PasskeysRails class method.\n\n```ruby\nPasskeysRails.subscribe(:did_register) do |event, agent, request|\n  # do something with the agent and/or request\nend\n```\n\n## Failure Codes\n\n1. In the event of authentication failure, **PasskeysRails** API endpoints render an error code and message.\n\n1. In a standard rails controller, the error code and message are rendered in JSON if `before_action :authenticate_passkey!` fails.\n\n1. In Grape, the error code and message are available in the result of the `PasskeysRails.authenticate(request)` method.\n\n1. From standard rails controllers, you can also access `passkey_authentication_result` to get the code and message.\n\n1. For `PasskeysRails.authenticate(request)` and `passkey_authentication_result`, the result is an object that respods to `.success?` and `.failure?`.\n - When `.success?` is true (`.failure?` is false), the resources is authentic and it also responds to `.agent`, returning a PasskeysRails::Agent\n - When `.success?` is false (`.failure?` is true), it responds to `.code` and `.message` to expose the error details.\n - When `.code` is `:missing_token`, `.message` is **X-Auth header is required**, which means the caller didn't supply the auth header.\n - When `.code` is `:invalid_token`, `.message` is **Invalid token - no agent exists with agent_id**, which means that the auth data is not valid.\n - When `.code` is `:expired_token`, `.message` is **The token has expired**, which means that the token is valid, but expired, thuis it's not considered authentic.\n - When `.code` is `:token_error`, `.message` is a description of the error.  This is a catch-all in the event we are unable to decode the token.\n\n In the future, the intention is to have the `.code` value stay consistent even if the `.message` changes.  This also allows you to localize the messages as needed using the code.\n\n## Testing\n\nPasskeysRails includes some test helpers for integration tests.  In order to use them, you need to include the module in your test cases/specs.\n\nIntegration test helpers are available by including the `PasskeysRails::IntegrationHelpers` module.\n\n```ruby\nclass PostTests \u003c ActionDispatch::IntegrationTest\n  include PasskeysRails::Test::IntegrationHelpers\nend\n```\nNow you can use the following `logged_in_headers` method in your integration tests.`\n\n```ruby\n  test 'authenticated users can see posts' do\n    user = User.create\n    get '/posts', headers: logged_in_headers('username-123', user)\n    assert_response :success\n  end\n```\n\nRSpec can include the `IntegrationHelpers` module in their `:feature` and `:request` specs.\n\n```ruby\nRSpec.configure do |config|\n  config.include PasskeysRails::Test::IntegrationHelpers, type: :feature\n  config.include PasskeysRails::Test::IntegrationHelpers, type: :request\nend\n```\n\n```ruby\nRSpec.describe 'Posts', type: :request do\n  let(:user) { User.create }\n  it \"allows authenticated users to see posts\" do\n    get '/posts', headers: logged_in_headers('username-123', user)\n    expect(response).to be_success\n  end\nend\n```\n\n## Mobile Application Integration\n\n### Prerequisites\n\nFor iOS, you need to associate your app with your server.  This amounts to setting up a special file on your server that defines the association.  See [setup your apple-app-site-association](#Ensure-`.well-known/apple-app-site-association`-is-in-place)\n\n\n### Mobile API Endpoints\n\nThere are 3 groups of API endpoints that your mobile application might consume.\n\n1. Unauthenticated (public) endpoints\n1. Authenticated (private) endpoints\n1. Passkey endpoints (for supporting authentication)\n\n**Unauthenticated endpoints** can be consumed without any authentication.\n\n**Authenticated endpoints** are protected by `authenticate_passkey!` or `PasskeysRails.authenticate!(request)`.  Those methods check for and validate the `X-Auth` header, which must be set to the auth token returned in the `AuthResponse`, described below.\n\n**Passkey endpoints** are supplied by this gem and allow you to register a user, authenticate (login) a user, and refresh the token.  This section describes these endpoints.\n\nThis gem supports the Passkey endpoints.\n\n### Passkey Endpoints\n\n* [POST /passkeys/challenge](post-passkeys-challenge)\n* [POST /passkeys/register](post-passkeys-register)\n* [POST /passkeys/authenticate](post-passkeys-authenticate)\n* [POST /passkeys/refresh](post-passkeys-refresh)\n* [POST /passkeys/debug_register](post-passkeys-debug-register)\n* [POST /passkeys/debug_login](post-passkeys-debug-login)\n\nAll Passkey endpoints accept and respond with JSON.\n\nOn **success**, they will respond with a 200 or 201 response code and relevant JSON.\n\nOn **error**, they will respond with a status code of `422` (Unprocessable Entity) and a JSON `ErrorResponse` structure:\n\n```JSON\n{\n  \"error\": {\n    \"context\": \"authentication\",\n    \"code\": \"Specific text code\",\n    \"message\": \"Some human readable message\"\n  }\n}\n```\n\nSome endpoints return an `AuthResponse`, which has this JSON structure:\n\n```JSON\n{\n    \"username\": String,   # the username used during registration\n    \"auth_token\": String  # an expiring token to use to authenticate with the back end (X-Auth header)\n}\n```\n\n### POST /passkeys/challenge\n\nSubmit this to begin registration or authentication.\n\n#### Registration (register)\n\nTo begin registration of a new credential, supply a `{ \"username\": \"unique username\" }`.\nIf all goes well, the JSON response will be the `options_for_create` from webauthn.\nIf the username is already in use, or anything else goes wrong, an error with code `validation_errors` will be returned.\n\nAfter receiving a successful response, follow up with a POST to `/passkeys/register`, below.\n\n#### Authentication (login)\nTo begin authenticating an existing credential, omit the `username`.  The JSON response will be the `options_for_get` from webauthn.\n\nAfter receiving a successful response, follow up with a POST to `/passkeys/authenticate`, below.\n\n### POST /passkeys/register\n\nAfter calling the `challenge` endpoint with a `username`, and handling its response, finish registering by calling this endpoint.\n\nSupply the following JSON structure:\n\n```JSON\n# POST body\n{\n  # NOTE: credential will likely come directly from the PassKeys class/library on the platform\n  \"credential\": {\n    \"id\": String,\n    \"rawId\": String,\n    \"type\": String,\n    \"response\": {\n      \"attestationObject\": String,\n      \"clientDataJSON\": String\n    }\n  },\n  # authenticatable is optional and is informas PasskeysRails how to build your \"user\" model\n  \"authenticatable\": { # optional\n    \"class\": \"User\", # whatever class to which you want this credential to apply (as described earlier)\n    \"params\": { }    # Any params you want passed as a hash to the registering_with method on that class\n  }\n}\n```\n\nOn **success**, the response is an `AuthResponse`.\n\nPossible **failure codes** (using the `ErrorResponse` structure) are:\n\n- `webauthn_error` - something is wrong with the credential\n- `error` - something else went wrong during credentail validation - see the `message` in the `ErrorResponse`\n- `passkey_error` - unable to persist the passkey\n- `invalid_authenticatable_class` - the supplied authenticatable class can't be created/found (check spelling \u0026 capitalization)\n- `invalid_class_whitelist` - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array\n- `invalid_authenticatable_class` - the supplied authenticatable class is not allowed - maybe it's not in the whitelist\n- `record_invalid` - the object of the supplied authenticatable class cannot be saved due to validation errors\n- `agent_not_found` - the agent referenced in the credential cannot be found in the database\n\n### POST /passkeys/authenticate\n\nAfter calling the `challenge` endpoint without a `username`, and handling its response, finish authenticating by calling this endpoint.\n\nSupply the following JSON structure:\n\n```JSON\n# POST body\n{\n  # NOTE: all of this will likely come directly from the PassKeys class/library on the platform\n  \"id\": String,                   # Base64 encoded assertion.credentialID\n  \"rawId\": String,                # Base64 encoded assertion.credentialID\n  \"type\": \"public-key\",\n  \"response\": {\n    \"authenticatorData\": String,  # Base64 encoded assertion.rawAuthenticatorData\n    \"clientDataJSON\": String,     # Base64 encoded assertion.rawClientDataJSON\n    \"signature\": String,          # Base64 encoded signature\n    \"userHandle\":String           # Base64 encoded assertion.userID\n  }\n}\n```\nOn **success**, the response is an `AuthResponse`.\n\nPossible **failure codes** (using the `ErrorResponse` structure) are:\n\n- `webauthn_error` - something is wrong with the credential\n- `passkey_not_found` - the passkey referenced in the credential cannot be found in the database\n\n### POST /passkeys/refresh\n\nThe token will expire after some time (configurable in `config/initializers/passkeys_rails.rb`).  Before that happens, refresh it using this API.  Once it expires, to get a new token, use the `/authentication` API.\n\nSupply the following JSON structure:\n\n```JSON\n# POST body\n{\n  token: String\n}\n```\n\nOn **success**, the response is an `AuthResponse` with a new, refreshed token.\n\nPossible **failure codes** (using the `ErrorResponse` structure) are:\n\n- `invalid_token` - the token data is invalid\n- `expired_token` - the token is expired\n- `token_error` - some other error ocurred when decoding the token\n\n### POST /passkeys/debug_register\n\nAs it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to register a username while bypassing the normal challenge/response sequence.\n\nThis endpoint only responds if `DEBUG_LOGIN_REGEX` is set in the server environment.  It is **very insecure to set this variable in a production environment** as it bypasses all Passkey checks.  It is only intended to be used during mobile application development.\n\nTo use this endpoint:\n\n1. Set `DEBUG_LOGIN_REGEX` to a regex that matches any username you want to use during development - for example `^test(-\\d+)?$` will match `test`, `test-1`, `test-123`, etc.\n\n1. In the mobile application, call this endpoint in stead of the `/passkeys/challenge` and `/passkeys/register`.  The response is identicial to that of `/passkeys/register`.\n\n1. Use the response as if it was from `/passkeys/register`.\n\nIf you supply a username that doesn't match the `DEBUG_LOGIN_REGEX`, the endpoint will respond with an error.\n\nSupply the following JSON structure:\n\n```JSON\n# POST body\n{\n  \"username\": String\n}\n```\nOn **success**, the response is an `AuthResponse`.\n\nPossible **failure codes** (using the `ErrorResponse` structure) are:\n\n- `not_allowed` - Invalid username (the username doesn't match the regex)\n- `invalid_authenticatable_class` - the supplied authenticatable class can't be created/found (check spelling \u0026 capitalization)\n- `invalid_class_whitelist` - the whitelist in the passkeys_rails.rb configuration is invalid - be sure it's nil or an array\n- `invalid_authenticatable_class` - the supplied authenticatable class is not allowed - maybe it's not in the whitelist\n- `record_invalid` - the object of the supplied authenticatable class cannot be saved due to validation errors\n\n### POST /passkeys/debug_login\n\nAs it may not be possible to acess Passkey functionality in mobile simulators, this endpoint may be called to login (authenticate) a username while bypassing the normal challenge/response sequence.\n\nThis endpoint only responds if `DEBUG_LOGIN_REGEX` is set in the server environment.  It is **very insecure to set this variable in a production environment** as it bypasses all Passkey checks.  It is only intended to be used during mobile application development.\n\nTo use this endpoint:\n\n1. Manually create one or more PasskeysRails::Agent records in the database.  A unique username is required for each.\n\n1. Set `DEBUG_LOGIN_REGEX` to a regex that matches any username you want to use during development - for example `^test(-\\d+)?$` will match `test`, `test-1`, `test-123`, etc.\n\n1. In the mobile application, call this endpoint in stead of the `/passkeys/challenge` and `/passkeys/authenticate`.  The response is identicial to that of `/passkeys/authenticate`.\n\n1. Use the response as if it was from `/passkeys/authenticate`.\n\nIf you supply a username that doesn't match the `DEBUG_LOGIN_REGEX`, the endpoint will respond with an error.\n\nSupply the following JSON structure:\n\n```JSON\n# POST body\n{\n  \"username\": String\n}\n```\nOn **success**, the response is an `AuthResponse`.\n\nPossible **failure codes** (using the `ErrorResponse` structure) are:\n\n- `not_allowed` - Invalid username (the username doesn't match the regex)\n- `agent_not_found` - No agent found with that username\n\n## Reference/Example Mobile Applications\n\nThere is a sample iOS app that integrates with **passkeys-rails** based server implementations.  It's a great place to get a quick start on implementing passkyes in your iOS, iPadOS or MacOS apps.\n\nCheck out the [PasskeysRailsDemo](https://github.com/alliedcode/PasskeysRailsDemo) app.\n\n## Contributing\n\n### Contribution Guidelines\n\nThank you for considering contributing to PasskeysRails! We welcome your help to improve and enhance this project. Whether it's a bug fix, documentation update, or a new feature, your contributions are valuable to the community.\n\nTo ensure a smooth collaboration, please follow the [Contribution Guidelines](https://github.com/alliedcode/passkeys-rails/blob/main/CONTRIBUTION_GUIDELINES.md) when submitting your contributions.\n\n### Code of Conduct\n\nPlease note that this project follows the [Code of Conduct](https://github.com/alliedcode/passkeys-rails/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. If you encounter any behavior that violates the code, please report it to the project maintainers.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://github.com/alliedcode/passkeys-rails/blob/main/MIT-LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falliedcode%2Fpasskeys-rails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falliedcode%2Fpasskeys-rails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falliedcode%2Fpasskeys-rails/lists"}