Skip to content

Commit

Permalink
Merge pull request #8 from kobaltz/passkey-login-no-password
Browse files Browse the repository at this point in the history
Passkey Login without email/password
  • Loading branch information
kobaltz authored Aug 14, 2024
2 parents a1fcf3e + e994b61 commit b84b8d3
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 8 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ ActionAuth.configure do |config|
config.allow_user_deletion = true
config.default_from_email = "[email protected]"
config.magic_link_enabled = true
config.passkey_only = true # Allows sign in with only a passkey
config.verify_email_on_sign_in = true
config.webauthn_enabled = true
config.webauthn_origin = "http://localhost:3000" # or "https://example.com"
Expand All @@ -127,6 +128,8 @@ These are the planned features for ActionAuth. The ones that are checked off are
✅ - Passkeys/Hardware Security Keys
✅ - Passkeys sign in without email/password
✅ - Magic Links
⏳ - OAuth with Google, Facebook, Github, Twitter, etc.
Expand All @@ -141,8 +144,6 @@ These are the planned features for ActionAuth. The ones that are checked off are
⏳ - Account Impersonation
## Usage
### Routes
Expand Down
4 changes: 3 additions & 1 deletion app/assets/javascripts/action_auth/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ const Credential = {

get: function (credentialOptions) {
const self = this;
const webauthnUrl = document.querySelector('meta[name="webauthn_auth_url"]').getAttribute("content");
const webauthnUrlTag = document.querySelector('meta[name="passkey_auth_url"]') ||
document.querySelector('meta[name="webauthn_auth_url"]');
const webauthnUrl = webauthnUrlTag.getAttribute("content");
WebAuthnJSON.get({ "publicKey": credentialOptions }).then(function (credential) {
self.callback(webauthnUrl, credential, "/");
});
Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/action_auth/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ body {
margin-right: 5px;
}
}

.container-fluid {
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
Expand Down Expand Up @@ -78,6 +79,11 @@ input[type="password"] {
margin-bottom: 1rem !important;
}

.mx-3 {
margin-left: 1rem !important;
margin-right: 1rem !important;
}

.btn {
padding: 0.375rem 0.75rem;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
Expand Down
24 changes: 24 additions & 0 deletions app/controllers/action_auth/sessions/passkeys_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module ActionAuth
module Sessions
class PasskeysController < ApplicationController
def new
get_options = WebAuthn::Credential.options_for_get
session[:current_challenge] = get_options.challenge
@options = get_options
end

def create
webauthn_credential = WebAuthn::Credential.from_get(params)
credential = WebauthnCredential.find_by(external_id: webauthn_credential.id)
user = User.find_by(id: credential&.user_id)
if credential && user
session = user.sessions.create
cookies.signed.permanent[:session_token] = { value: session.id, httponly: true }
redirect_to main_app.root_path(format: :html), notice: "Signed in successfully"
else
redirect_to sign_in_path(format: :html), alert: "That passkey is incorrect" and return
end
end
end
end
end
9 changes: 7 additions & 2 deletions app/views/action_auth/magics/requests/new.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1>Sign up</h1>
<h1>Request Magic Link</h1>

<%= form_with(url: magics_requests_path) do |form| %>
<div class="mb-3">
Expand All @@ -8,11 +8,16 @@

<div class="mb-3">
<%= form.submit "Request Magic Link", class: "btn btn-primary" %>
<span class="mx-3">or</span>
<%= link_to "Sign In", sign_in_path %>
<% if ActionAuth.configuration.passkey_only? %>
<span class="mx-3">or</span>
<%= link_to "Passkey", new_sessions_passkey_path %>
<% end %>
</div>
<% end %>

<div class="mb-3">
<%= link_to "Sign In", sign_in_path %> |
<%= link_to "Sign Up", sign_up_path %> |
<%= link_to "Reset Password", new_identity_password_reset_path %>
<% if ActionAuth.configuration.verify_email_on_sign_in %>
Expand Down
3 changes: 3 additions & 0 deletions app/views/action_auth/registrations/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<% if ActionAuth.configuration.magic_link_enabled? %>
<%= link_to "Magic Link", new_magics_requests_path %> |
<% end %>
<% if ActionAuth.configuration.passkey_only? %>
<%= link_to "Passkey", new_sessions_passkey_path %> |
<% end %>
<%= link_to "Reset Password", new_identity_password_reset_path %>
<% if ActionAuth.configuration.verify_email_on_sign_in %>
| <%= link_to "Verify Email", identity_email_verification_path %>
Expand Down
11 changes: 8 additions & 3 deletions app/views/action_auth/sessions/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@

<div class="mb-3">
<%= form.submit "Sign in", class: "btn btn-primary" %>
<% if ActionAuth.configuration.magic_link_enabled? %>
<span class="mx-3">or</span>
<%= link_to "Magic Link", new_magics_requests_path %>
<% end %>
<% if ActionAuth.configuration.passkey_only? %>
<span class="mx-3">or</span>
<%= link_to "Passkey", new_sessions_passkey_path %>
<% end %>
</div>
<% end %>

<div class="mb-3">
<%= link_to "Sign Up", sign_up_path %> |
<% if ActionAuth.configuration.magic_link_enabled? %>
<%= link_to "Magic Link", new_magics_requests_path %> |
<% end %>
<%= link_to "Reset Password", new_identity_password_reset_path %>
<% if ActionAuth.configuration.verify_email_on_sign_in %>
| <%= link_to "Verify Email", identity_email_verification_path %>
Expand Down
20 changes: 20 additions & 0 deletions app/views/action_auth/sessions/passkeys/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<h2 class="action-auth--text-center">Use a passkey to sign in</h2>
<%= tag :meta, name: :passkey_auth_url, content: action_auth.sessions_passkeys_url %>
<%= content_tag :div,
id: "webauthn_credential_form",
data: {
controller: "credential-authenticator",
"credential-authenticator-options-value": @options
},
class: "action-auth--text-center" do %>

<div class="mb-3 action-auth--text-center">
Insert a USB key, if necessary, and tap it.
An account with a matching passkey is required.
</div>
<% end %>
<%= content_for :cancel_path do %>
<%= link_to "Cancel", action_auth.sign_in_path %>
<% end %>
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
resource :password_reset, only: [:new, :edit, :create, :update]
end
resource :password, only: [:edit, :update]
namespace :sessions do
if ActionAuth.configuration.webauthn_enabled? && ActionAuth.configuration.passkey_only?
resources :passkeys, only: [:new, :create]
end
end
resources :sessions, only: [:index, :show, :destroy]

if ActionAuth.configuration.allow_user_deletion?
Expand Down
5 changes: 5 additions & 0 deletions lib/action_auth/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def initialize
@allow_user_deletion = true
@default_from_email = "[email protected]"
@magic_link_enabled = true
@passkey_only = true
@pwned_enabled = defined?(Pwned)
@verify_email_on_sign_in = true
@webauthn_enabled = defined?(WebAuthn)
Expand All @@ -29,6 +30,10 @@ def magic_link_enabled?
@magic_link_enabled == true
end

def passkey_only?
webauthn_enabled? && @passkey_only == true
end

def webauthn_enabled?
@webauthn_enabled.respond_to?(:call) ? @webauthn_enabled.call : @webauthn_enabled
end
Expand Down

0 comments on commit b84b8d3

Please sign in to comment.