Add passkey option on TOTP page and auto-trigger passkey for TOTP users
When a user has both passkeys and TOTP configured, auto-trigger the passkey flow on login to save them from the password→TOTP path. Also add a "Use Passkey Instead" button on the TOTP verification page as an escape hatch for users who end up there. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,10 @@ class SessionsController < ApplicationController
|
||||
nil
|
||||
end
|
||||
|
||||
# Pass data to the view for passkey option
|
||||
@user_has_webauthn = user&.can_authenticate_with_webauthn?
|
||||
@pending_email = user&.email_address
|
||||
|
||||
# Just render the form
|
||||
end
|
||||
|
||||
|
||||
@@ -148,7 +148,8 @@ class WebauthnController < ApplicationController
|
||||
# Only return minimal necessary info - no user_id or preferred_method
|
||||
render json: {
|
||||
has_webauthn: user.can_authenticate_with_webauthn?,
|
||||
requires_webauthn: user.require_webauthn?
|
||||
requires_webauthn: user.require_webauthn?,
|
||||
has_totp: user.totp_enabled?
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -49,8 +49,9 @@ export default class extends Controller {
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-trigger passkey authentication if required
|
||||
if (data.requires_webauthn) {
|
||||
// Auto-trigger passkey authentication if required, or if user has both
|
||||
// webauthn and TOTP (to save them from the password→TOTP flow)
|
||||
if (data.requires_webauthn || (data.has_webauthn && data.has_totp)) {
|
||||
setTimeout(() => this.authenticate(), 100);
|
||||
}
|
||||
} else {
|
||||
@@ -289,6 +290,10 @@ export default class extends Controller {
|
||||
if (!emailInput) {
|
||||
emailInput = document.querySelector('input[name="user[email_address]"]');
|
||||
}
|
||||
// Fallback to hidden webauthn_email field (e.g., on TOTP verification page)
|
||||
if (!emailInput) {
|
||||
emailInput = document.querySelector('input[name="webauthn_email"]');
|
||||
}
|
||||
return emailInput ? emailInput.value.trim() : "";
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,31 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @user_has_webauthn %>
|
||||
<div data-controller="webauthn" data-webauthn-check-url-value="/webauthn/check">
|
||||
<input type="hidden" name="webauthn_email" value="<%= @pending_email %>">
|
||||
<div class="mt-4">
|
||||
<div class="relative my-4">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
<div class="w-full border-t border-gray-300"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center text-sm">
|
||||
<span class="px-2 bg-white text-gray-500">Or</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
data-action="click->webauthn#authenticate"
|
||||
class="w-full rounded-md px-3.5 py-2.5 bg-green-600 hover:bg-green-500 text-white font-medium cursor-pointer flex items-center justify-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
</svg>
|
||||
Use Passkey Instead
|
||||
</button>
|
||||
<div data-webauthn-target="error" class="mt-2 text-sm text-red-600" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center">
|
||||
|
||||
Reference in New Issue
Block a user