Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passkey Login without email/password #8

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = "from@example.com"
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 = "from@example.com"
@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
Loading