PasskeyAuth
A Rails engine that adds passwordless authentication methods to your Rails app using WebAuthn passkeys and magic links with email verification codes. Works seamlessly alongside Rails' built-in authentication system.
Features
- Complements Rails Authentication: Works with
rails generate authentication- doesn't replace it - Passwordless Options: Give users alternatives to password authentication
- WebAuthn/Passkey Support: Modern biometric and hardware-key authentication
- Magic Links: Email-based authentication with short verification codes
- Rate Limiting: Built-in protection against abuse
- Customizable Hooks: Add your own behavior to authentication events
- I18n Support: Fully internationalized with English translations included
- Rails 8+ Compatible: Designed for modern Rails applications
Installation
Add this line to your application's Gemfile:
gem 'passkey_auth'
And then execute:
bundle install
Or install it yourself as:
gem install passkey_auth
Setup
1. Install Rails Authentication (if not already done)
PasskeyAuth requires Rails' authentication system. If you haven't already:
rails generate authentication
This creates the base authentication system with sessions, User model, and the Authentication concern.
Important: Ensure your Rails app has a root route defined in config/routes.rb. PasskeyAuth redirects users to the root URL after authentication.
2. Install PasskeyAuth
rails generate passkey_auth:install
This will:
- Create migrations for magic links and WebAuthn credentials
- Add passwordless fields to your users table
- Add an initializer at
config/initializers/passkey_auth.rb - Mount the engine routes
- Copy JavaScript controllers
3. Add to your User model
class User < ApplicationRecord
has_secure_password # From Rails authentication
include PasskeyAuth::Concerns::Passwordless # Add passwordless methods
# Optional: If you prefer using :email instead of :email_address
alias_attribute :email, :email_address
end
Note: Rails 8's authentication uses email_address as the attribute name. PasskeyAuth defaults to this, but you can configure it in the initializer if your app uses a different attribute name.
4. Register Stimulus Controllers
Add the PasskeyAuth Stimulus controllers to your app/javascript/controllers/application.js:
import { application } from "./application"
import { PasskeyAuthenticationController, WebauthnRegisterController, WebauthnAuthController }
from "passkey_auth/controllers"
// Register PasskeyAuth controllers
application.register("passkey-authentication", PasskeyAuthenticationController)
application.register("webauthn-register", WebauthnRegisterController)
application.register("webauthn-auth", WebauthnAuthController)
Note: The installer automatically adds the required JavaScript dependencies to your importmap. If you're using a different bundler (esbuild, webpack, etc.), install the WebAuthn dependency:
npm install @github/webauthn-json
5. Run migrations
rails db:migrate
Usage
How It Works
PasskeyAuth provides alternative authentication methods that work alongside password-based auth:
- Password auth (Rails):
SessionsControllerhandles traditional email/password login - Magic links (PasskeyAuth): Users can request email-based login codes
- Passkeys (PasskeyAuth): Users can register biometric/hardware keys for login
All three methods use the same session management from Rails' Authentication concern. You simply give users more options for how they authenticate.
Session Management
PasskeyAuth uses Rails' session management methods from the Authentication concern:
current_user- Access the authenticated userstart_new_session_for(user)- Log a user interminate_session- Log the user outallow_unauthenticated_access- Allow public access to actions
These are provided by Rails' Authentication concern in your ApplicationController.
Magic Links
Offer magic link authentication as an alternative to passwords:
<!-- In your login page -->
<%= link_to "Sign in with email code", passkey_auth.new_magic_link_path %>
Users can then:
- Enter their email address
- Receive an email with a magic link and short code
- Click the link or enter the code to authenticate
The magic link flow:
GET /auth/magic_links/new- Request formPOST /auth/magic_links- Send magic link emailGET /auth/magic_links/verify_code- Code entry formPOST /auth/magic_links/verify_with_code- Verify codeGET /auth/magic_links/:token- Verify token from email link
Passkeys (WebAuthn)
Configure the WebAuthn origin in the initializer:
PasskeyAuth.setup do |config|
config.webauthn_origin = ENV.fetch("WEBAUTHN_ORIGIN", "http://localhost:3000")
config.webauthn_rp_name = "Your App Name"
end
Adding Passkey Sign-In Button
Add the passkey authentication UI to your sign-in page:
<div data-controller="passkey-authentication"
data-passkey-authentication-challenge-path-value="<%= passkey_auth.webauthn_credentials_challenge_path(format: :json) %>"
data-passkey-authentication-authentication-path-value="<%= passkey_auth.webauthn_authentications_path(format: :json) %>">
<button type="button"
data-passkey-authentication-target="signinButton"
data-action="click->passkey-authentication#signIn">
Sign in with Passkey
</button>
</div>
Registering a New Passkey
Allow users to register new passkeys in their profile:
<%= form_with url: passkey_auth.webauthn_credentials_path,
data: {
controller: "webauthn-register",
webauthn_register_callback_url_value: passkey_auth.webauthn_credentials_path,
action: "submit->webauthn-register#submit"
} do |form| %>
<%= form.text_field :nickname, placeholder: "Name this passkey", data: { webauthn_register_target: "nickname" } %>
<%= form.submit "Add Passkey" %>
<% end %>
Configuration
Edit config/initializers/passkey_auth.rb:
PasskeyAuth.setup do |config|
# User model (default: "User")
config.user_model_name = "User"
# Email attribute on User model (default: :email_address)
# Rails 8 uses :email_address, but you can change this if your app uses :email
# Or use alias_attribute in your User model instead
config.user_email_attribute = :email_address
# Magic link expiration (default: 10.minutes)
config.magic_link_expiration = 10.minutes
# Rate limit for magic links (default: 1.minute)
config.magic_link_rate_limit = 1.minute
# WebAuthn settings
config.webauthn_origin = ENV.fetch("WEBAUTHN_ORIGIN")
config.webauthn_rp_name = "Your App"
end
Email Attribute Configuration
Rails 8's authentication uses email_address as the default attribute name. PasskeyAuth respects this default, but you have two options if you prefer using email:
Option 1: Use alias_attribute in your User model (recommended)
class User < ApplicationRecord
has_secure_password
include PasskeyAuth::Concerns::Passwordless
alias_attribute :email, :email_address
end
Option 2: Configure PasskeyAuth to use :email
PasskeyAuth.setup do |config|
config.user_email_attribute = :email
end
Hooks
Add custom behavior to authentication events:
PasskeyAuth.setup do |config|
config.on_magic_link_requested = ->(user, magic_link) {
# Track magic link requests
Analytics.track(user_id: user.id, event: 'magic_link_requested')
}
config.on_passkey_created = ->(user, credential) {
# Track passkey creation
Analytics.track(user_id: user.id, event: 'passkey_created')
}
end
Customization
Views
Override any view by creating a file at the corresponding path in your app:
app/views/passkey_auth/magic_links/new.html.erb
app/views/passkey_auth/magic_links/verify_code.html.erb
app/views/passkey_auth/webauthn/credentials/index.html.erb
Mailers
Override the magic link email template:
app/views/passkey_auth/magic_link_mailer/login_link.html.erb
Routes
The engine is mounted at /auth by default. Change this in config/routes.rb:
mount PasskeyAuth::Engine => "/authentication"
Security Considerations
- Magic links expire after 10 minutes by default
- Magic links can only be used once
- Rate limiting prevents abuse (1 request per minute by default)
- Sessions are managed by Rails' Authentication concern
- WebAuthn credentials use public key cryptography
- All sensitive operations use database transactions
Architecture
PasskeyAuth is designed to complement, not replace, Rails authentication:
What Rails Provides:
Authenticationconcern for session managementSessionsControllerfor password-based logincurrent_user,start_new_session_for, etc.- User model with
has_secure_password
What PasskeyAuth Adds:
Passwordlessconcern with magic link and passkey methods- Controllers for magic link and WebAuthn flows
- Models for storing magic links and WebAuthn credentials
- JavaScript for browser WebAuthn integration
Users can authenticate with any of these methods, and all use the same session system.
Requirements
- Ruby >= 3.2
- Rails >= 8.0 (with
rails generate authenticationinstalled) - PostgreSQL, MySQL, or SQLite
Development
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/passkey_auth. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the PasskeyAuth project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.