devise-passkeys 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +47 -2
- data/CONTRIBUTING.md +47 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -38
- data/README.md +24 -23
- data/THANKS.md +9 -0
- data/devise-passkeys.gemspec +2 -2
- data/gemfiles/rails_6.gemfile +2 -0
- data/gemfiles/rails_7.gemfile +2 -0
- data/lib/devise/passkeys/controllers/concerns/reauthentication.rb +93 -0
- data/lib/devise/passkeys/controllers/concerns/reauthentication_challenge.rb +26 -0
- data/lib/devise/passkeys/controllers/passkeys_controller_concern.rb +18 -13
- data/lib/devise/passkeys/controllers/reauthentication_controller_concern.rb +70 -4
- data/lib/devise/passkeys/controllers/registrations_controller_concern.rb +146 -7
- data/lib/devise/passkeys/controllers/sessions_controller_concern.rb +3 -3
- data/lib/devise/passkeys/controllers.rb +55 -1
- data/lib/devise/passkeys/model.rb +9 -1
- data/lib/devise/passkeys/passkey_issuer.rb +0 -2
- data/lib/devise/passkeys/reauthentication_strategy.rb +7 -0
- data/lib/devise/passkeys/strategy.rb +1 -1
- data/lib/devise/passkeys/version.rb +1 -1
- data/lib/devise/passkeys.rb +22 -5
- metadata +10 -7
- data/lib/devise/passkeys/controllers/concerns/passkey_reauthentication.rb +0 -39
@@ -7,16 +7,16 @@ module Devise
|
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
include Devise::Passkeys::Controllers::Concerns::
|
10
|
+
include Devise::Passkeys::Controllers::Concerns::Reauthentication
|
11
11
|
include Devise::Passkeys::Controllers::Concerns::ReauthenticationChallenge
|
12
12
|
include Warden::WebAuthn::AuthenticationInitiationHelpers
|
13
13
|
include Warden::WebAuthn::RegistrationHelpers
|
14
|
-
include Warden::WebAuthn::StrategyHelpers
|
15
14
|
|
16
15
|
prepend_before_action :authenticate_scope!
|
17
16
|
before_action :ensure_at_least_one_passkey, only: %i[new_destroy_challenge destroy]
|
18
17
|
before_action :find_passkey, only: %i[new_destroy_challenge destroy]
|
19
18
|
|
19
|
+
before_action :verify_credential_integrity, only: [:create]
|
20
20
|
before_action :verify_passkey_challenge, only: [:create]
|
21
21
|
before_action :verify_reauthentication_token, only: %i[create destroy]
|
22
22
|
|
@@ -93,18 +93,17 @@ module Devise
|
|
93
93
|
{ id: resource.webauthn_id, name: resource.email }
|
94
94
|
end
|
95
95
|
|
96
|
+
def verify_credential_integrity
|
97
|
+
return render_credential_missing_or_could_not_be_parsed_error if parsed_credential.nil?
|
98
|
+
rescue JSON::JSONError, TypeError
|
99
|
+
return render_credential_missing_or_could_not_be_parsed_error
|
100
|
+
end
|
101
|
+
|
96
102
|
def verify_passkey_challenge
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
102
|
-
begin
|
103
|
-
@webauthn_credential = verify_registration(relying_party: relying_party)
|
104
|
-
rescue ::WebAuthn::Error => e
|
105
|
-
error_key = Warden::WebAuthn::ErrorKeyFinder.webauthn_error_key(exception: e)
|
106
|
-
render json: { message: find_message(error_key) }, status: :bad_request
|
107
|
-
end
|
103
|
+
@webauthn_credential = verify_registration(relying_party: relying_party)
|
104
|
+
rescue ::WebAuthn::Error => e
|
105
|
+
error_key = Warden::WebAuthn::ErrorKeyFinder.webauthn_error_key(exception: e)
|
106
|
+
render json: { message: find_message(error_key) }, status: :bad_request
|
108
107
|
end
|
109
108
|
|
110
109
|
def passkey_params
|
@@ -134,6 +133,12 @@ module Devise
|
|
134
133
|
def reauthentication_params
|
135
134
|
params.require(:passkey).permit(:reauthentication_token)
|
136
135
|
end
|
136
|
+
|
137
|
+
def render_credential_missing_or_could_not_be_parsed_error
|
138
|
+
render json: { message: find_message(:credential_missing_or_could_not_be_parsed) }, status: :bad_request
|
139
|
+
delete_registration_challenge
|
140
|
+
return false
|
141
|
+
end
|
137
142
|
end
|
138
143
|
end
|
139
144
|
end
|
@@ -3,14 +3,44 @@
|
|
3
3
|
module Devise
|
4
4
|
module Passkeys
|
5
5
|
module Controllers
|
6
|
+
# This concern is responsible for handling reauthentication.
|
7
|
+
# It should be included in any controller that handles reauthentication, and defines:
|
8
|
+
#
|
9
|
+
# - Useful methods to assist with the reauthentication process
|
10
|
+
# - Concerns that are required to complete the reauthentication process
|
11
|
+
# - Helper modules from `Warden::WebAuthn` that are required to complete the reauthentication process
|
12
|
+
#
|
13
|
+
# **Note**: the implementing controller **must** define a `relying_party` method in order for
|
14
|
+
# reauthentications to work.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# class ReauthenticationController < ApplicationController
|
18
|
+
# include Devise::Passkeys::Controllers::ReauthenticationControllerConcern
|
19
|
+
#
|
20
|
+
# def relying_party
|
21
|
+
# WebAuthn::RelyingParty.new
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# The `authenticate_scope!` is called as a `before_action` to verify the authentication and set the
|
26
|
+
# `resource` for the controller.
|
27
|
+
#
|
28
|
+
# Likewise, `Warden::WebAuthn::RackHelpers#set_relying_party_in_request_env` is a `before_action` to ensure that the relying party is set in the
|
29
|
+
# `request.env` before the Warden strategy is executed
|
30
|
+
#
|
31
|
+
# @see relying_party
|
32
|
+
# @see Devise::Passkeys::Controllers::Concerns::ReauthenticationChallenge
|
33
|
+
# @see Devise::Passkeys::Controllers::Concerns::Reauthentication
|
34
|
+
# @see Warden::WebAuthn::StrategyHelpers
|
35
|
+
# @see Warden::WebAuthn::RackHelpers
|
6
36
|
module ReauthenticationControllerConcern
|
7
37
|
extend ActiveSupport::Concern
|
8
38
|
|
9
39
|
included do
|
10
|
-
include Devise::Passkeys::Controllers::Concerns::
|
40
|
+
include Devise::Passkeys::Controllers::Concerns::Reauthentication
|
11
41
|
include Devise::Passkeys::Controllers::Concerns::ReauthenticationChallenge
|
12
42
|
include Warden::WebAuthn::AuthenticationInitiationHelpers
|
13
|
-
include Warden::WebAuthn::
|
43
|
+
include Warden::WebAuthn::RackHelpers
|
14
44
|
|
15
45
|
prepend_before_action :authenticate_scope!
|
16
46
|
|
@@ -27,6 +57,14 @@ module Devise
|
|
27
57
|
end
|
28
58
|
end
|
29
59
|
|
60
|
+
# A controller action that stores the reauthentication challenge in session
|
61
|
+
# and renders the options for authentication from `webauthn-ruby`.
|
62
|
+
#
|
63
|
+
# The response is rendered as JSON, with a status of `200 OK`.
|
64
|
+
#
|
65
|
+
# @see Devise::Passkeys::Controllers::Concerns::ReauthenticationChallenge#store_reauthentication_challenge_in_session
|
66
|
+
# @see Warden::WebAuthn::AuthenticationInitiationHelpers#generate_authentication_options
|
67
|
+
# @see Warden::WebAuthn::RackHelpers#set_relying_party_in_request_env
|
30
68
|
def new_challenge
|
31
69
|
options_for_authentication = generate_authentication_options(relying_party: relying_party,
|
32
70
|
options: { allow: resource.passkeys.pluck(:external_id) })
|
@@ -36,6 +74,24 @@ module Devise
|
|
36
74
|
render json: options_for_authentication
|
37
75
|
end
|
38
76
|
|
77
|
+
# A controller action that:
|
78
|
+
#
|
79
|
+
# 1. Uses the `warden` strategy to authenticate the current user with the defined strategy
|
80
|
+
# 2. Calls `sign_in` with `event: :passkey_reauthentication` to verify that the user can authenticate
|
81
|
+
# 3. Stores the reauthentication token in the session
|
82
|
+
# 4. Renders a JSON object with the reauthentication token
|
83
|
+
# 5. Ensures that the reauthentication challenge from the session, regardless of any errors
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# {"reauthentication_token": "abcd1234"}
|
87
|
+
#
|
88
|
+
# `prepare_params` is called as a `before_action` to prepare the passkey credential for use by the
|
89
|
+
# Warden strategy.
|
90
|
+
#
|
91
|
+
# Optionally accepts a block that will be executed after the user has been reauthenticated.
|
92
|
+
# @see strategy
|
93
|
+
# @see Devise::Passkeys::Controllers::Concerns::Reauthentication#store_reauthentication_token_in_session
|
94
|
+
# @see prepare_params
|
39
95
|
def reauthenticate
|
40
96
|
sign_out(resource)
|
41
97
|
self.resource = warden.authenticate!(strategy, auth_options)
|
@@ -51,12 +107,18 @@ module Devise
|
|
51
107
|
|
52
108
|
protected
|
53
109
|
|
110
|
+
# @!visibility public
|
111
|
+
# Prepares the request parameters for use by the Warden strategy
|
54
112
|
def prepare_params
|
55
113
|
request.params[resource_name] = ActionController::Parameters.new({
|
56
114
|
passkey_credential: params[:passkey_credential]
|
57
115
|
})
|
58
116
|
end
|
59
117
|
|
118
|
+
# @!visibility public
|
119
|
+
# A method that can be overridden to customize the Warden stratey used.
|
120
|
+
# @return [Symbol] The key that identifies which `Warden` strategy will be used to handle the
|
121
|
+
# authentication flow for the reauthentication. Defaults to `:passkey_reauthentication`
|
60
122
|
def strategy
|
61
123
|
:passkey_reauthentication
|
62
124
|
end
|
@@ -69,8 +131,12 @@ module Devise
|
|
69
131
|
session.delete(passkey_reauthentication_challenge_session_key)
|
70
132
|
end
|
71
133
|
|
72
|
-
|
73
|
-
|
134
|
+
# @!visibility public
|
135
|
+
# @abstract
|
136
|
+
# The method that returns the `WebAuthn::RelyingParty` for this request.
|
137
|
+
# @return [WebAuthn::RelyingParty] when overridden, this method should return a `WebAuthn::RelyingParty` instance
|
138
|
+
def relying_party
|
139
|
+
raise NoMethodError, "need to define relying_party for this #{self.class.name}"
|
74
140
|
end
|
75
141
|
end
|
76
142
|
end
|
@@ -3,11 +3,43 @@
|
|
3
3
|
module Devise
|
4
4
|
module Passkeys
|
5
5
|
module Controllers
|
6
|
+
# This concern should be included in any controller that handles
|
7
|
+
# user (`resource`) registration management (ie: signup/deleting an account),
|
8
|
+
# and defines:
|
9
|
+
#
|
10
|
+
# - Useful methods and before filters to streamline user (`resource`) registration management using session variables
|
11
|
+
# - Controller actions for:
|
12
|
+
# - Issuing a new WebAuthn challenge
|
13
|
+
# - A `create` action that creates a passkey if the user (`resource`) has been persisted
|
14
|
+
# - Helper modules from `Warden::WebAuthn` that are required to complete the registration process
|
15
|
+
#
|
16
|
+
# The `registration_user_id_key` and `registration_challenge_key` are defined
|
17
|
+
# using the `resource_name`, to keep the generated IDs unique between resources
|
18
|
+
# during the registration process.
|
19
|
+
#
|
20
|
+
# A `raw_credential` method is provided to streamline access to
|
21
|
+
# `passkey_params[:passkey_credential]`.
|
22
|
+
#
|
23
|
+
# **Note**: the implementing controller **must** define a `relying_party` method in order for
|
24
|
+
# registrations to work.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# class RegistrationsController < ApplicationController
|
28
|
+
# include Devise::Passkeys::Controllers::RegistrationsControllerConcern
|
29
|
+
#
|
30
|
+
# def relying_party
|
31
|
+
# WebAuthn::RelyingParty.new
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# @see Devise::Passkeys::Controllers::Concerns::Reauthentication
|
37
|
+
# @see Warden::WebAuthn::RegistrationHelpers
|
6
38
|
module RegistrationsControllerConcern
|
7
39
|
extend ActiveSupport::Concern
|
8
40
|
|
9
41
|
included do
|
10
|
-
include Devise::Passkeys::Controllers::Concerns::
|
42
|
+
include Devise::Passkeys::Controllers::Concerns::Reauthentication
|
11
43
|
include Warden::WebAuthn::RegistrationHelpers
|
12
44
|
|
13
45
|
before_action :require_no_authentication, only: [:new_challenge]
|
@@ -30,6 +62,20 @@ module Devise
|
|
30
62
|
end
|
31
63
|
end
|
32
64
|
|
65
|
+
# This controller action issues a new challenge for the registration handshake.
|
66
|
+
#
|
67
|
+
# The challenge is stored in a session variable, and renders the WebAuthn
|
68
|
+
# registration options as a JSON response.
|
69
|
+
#
|
70
|
+
# The following before filters are called:
|
71
|
+
#
|
72
|
+
# - `require_no_authentication`
|
73
|
+
# - `require_email_and_passkey_label`
|
74
|
+
#
|
75
|
+
# @see DeviseController#require_no_authentication
|
76
|
+
# @see require_email_and_passkey_label
|
77
|
+
# @see Warden::WebAuthn#generate_registration_options
|
78
|
+
# @see https://github.com/cedarcode/webauthn-ruby#initiation-phase
|
33
79
|
def new_challenge
|
34
80
|
options_for_registration = generate_registration_options(
|
35
81
|
relying_party: relying_party,
|
@@ -42,15 +88,48 @@ module Devise
|
|
42
88
|
render json: options_for_registration
|
43
89
|
end
|
44
90
|
|
91
|
+
# This controller action creates a new user (`resource`), using the given
|
92
|
+
# email & passkey. It:
|
93
|
+
#
|
94
|
+
# 1. calls the parent class's `#create` method
|
95
|
+
# 2. calls `#create_passkey_for_resource` to finish creating the passkey
|
96
|
+
# if the user (`resource`) was actually persisted
|
97
|
+
# 3. Finishes the rest of the parent class's `#create` method
|
98
|
+
#
|
99
|
+
#
|
100
|
+
# The following before actions are called:
|
101
|
+
#
|
102
|
+
# - `require_email_and_passkey_label`
|
103
|
+
# - `verify_passkey_registration_challenge`
|
104
|
+
# - `configure_sign_up_params`
|
105
|
+
#
|
106
|
+
# @see require_email_and_passkey_label
|
107
|
+
# @see verify_passkey_registration_challenge
|
108
|
+
# @see configure_sign_up_params
|
109
|
+
# @see create_passkey_for_resource
|
45
110
|
def create
|
46
111
|
super do |resource|
|
47
|
-
|
112
|
+
create_passkey_for_resource(resource: resource)
|
48
113
|
end
|
49
114
|
end
|
50
115
|
|
51
116
|
protected
|
52
117
|
|
53
|
-
|
118
|
+
# @!visibility public
|
119
|
+
#
|
120
|
+
# Creates a passkey for given user (`resource`).
|
121
|
+
#
|
122
|
+
# The method tests that the user (`resource`) is in the database
|
123
|
+
# before saving the passkey for the given user (`resource`).
|
124
|
+
#
|
125
|
+
#
|
126
|
+
# This method also ensures that the generated WebAuthn User ID is deleted from the session to prevent
|
127
|
+
# data leaks.
|
128
|
+
#
|
129
|
+
#
|
130
|
+
# @yield [resource, passkey] The provided `resource` and the newly created passkey.
|
131
|
+
# @see create_passkey
|
132
|
+
def create_passkey_for_resource(resource:)
|
54
133
|
return unless resource.persisted?
|
55
134
|
|
56
135
|
passkey = create_passkey(resource: resource)
|
@@ -59,6 +138,17 @@ module Devise
|
|
59
138
|
delete_registration_user_id!
|
60
139
|
end
|
61
140
|
|
141
|
+
# @!visibility public
|
142
|
+
#
|
143
|
+
# Generates a passkey for the given `resource`, using the `resource.passkeys.create!`
|
144
|
+
# method with the following attributes:
|
145
|
+
#
|
146
|
+
# - `label`: The `passkey_params[:passkey_label]`
|
147
|
+
# - `public_key`: The `@webauthn_credential.public_key`
|
148
|
+
# - `external_id`: The credential ID, strictly encoded as a Base 64 string
|
149
|
+
# - `sign_count`: The `@webauthn_credential.sign_count`
|
150
|
+
# - `last_used_at`: The current time, since this is the first time the passkey is being used
|
151
|
+
#
|
62
152
|
def create_passkey(resource:)
|
63
153
|
resource.passkeys.create!(
|
64
154
|
label: passkey_params[:passkey_label],
|
@@ -69,29 +159,61 @@ module Devise
|
|
69
159
|
)
|
70
160
|
end
|
71
161
|
|
162
|
+
# @!visibility public
|
163
|
+
#
|
164
|
+
# Verifies that the given reauthentication token matches the
|
165
|
+
# expected value stored in the session.
|
166
|
+
#
|
167
|
+
# If the reauthentication token is not valid,
|
168
|
+
# a `400 Bad Request` JSON response is rendered.
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# {"error": "Please reauthenticate to continue."}
|
172
|
+
#
|
173
|
+
# @see reauthentication_params
|
174
|
+
# @see Devise::Passkeys::Controllers::Concerns::Reauthentication#valid_reauthentication_token?
|
72
175
|
def verify_reauthentication_token
|
73
176
|
return if valid_reauthentication_token?(given_reauthentication_token: reauthentication_params[:reauthentication_token])
|
74
177
|
|
75
178
|
render json: { error: find_message(:not_reauthenticated) }, status: :bad_request
|
76
179
|
end
|
77
180
|
|
181
|
+
# @!visibility public
|
182
|
+
# The subset of parameters used when verifying a reauthentication_token
|
78
183
|
def reauthentication_params
|
79
|
-
params.require(
|
184
|
+
params.require(resource_name).permit(:reauthentication_token)
|
80
185
|
end
|
81
186
|
|
187
|
+
# @!visibility public
|
188
|
+
# An override of `DeviseController`'s implementation, to circumvent the
|
189
|
+
# `update_with_password` method
|
190
|
+
# @see DeviseController#update_resource
|
82
191
|
def update_resource(resource, params)
|
83
192
|
resource.update(params)
|
84
193
|
end
|
85
194
|
|
86
|
-
#
|
195
|
+
# @!visibility public
|
196
|
+
# Override this method if you need to exclude certain WebAuthn credentials
|
197
|
+
# from a registration request.
|
198
|
+
# @see new_challenge
|
199
|
+
# @see https://github.com/cedarcode/webauthn-ruby#initiation-phase
|
87
200
|
def exclude_external_ids_for_registration
|
88
201
|
[]
|
89
202
|
end
|
90
203
|
|
204
|
+
# @!visibility public
|
205
|
+
# The subset of parameters used when verifying the passkey
|
91
206
|
def passkey_params
|
92
207
|
params.require(resource_name).permit(:passkey_label, :passkey_credential)
|
93
208
|
end
|
94
209
|
|
210
|
+
# @!visibility public
|
211
|
+
# Verifies that the `sign_up_params` has an `:email` and `:passkey_label`.
|
212
|
+
#
|
213
|
+
# If either is missing or blank, a `400 Bad Request` JSON response is rendered.
|
214
|
+
#
|
215
|
+
# @example
|
216
|
+
# {"error": "Please enter your email address."}
|
95
217
|
def require_email_and_passkey_label
|
96
218
|
if sign_up_params[:email].blank?
|
97
219
|
render json: { message: find_message(:email_missing) }, status: :bad_request
|
@@ -106,6 +228,18 @@ module Devise
|
|
106
228
|
true
|
107
229
|
end
|
108
230
|
|
231
|
+
# @!visibility public
|
232
|
+
# Verifies the registration challenge is correct.
|
233
|
+
#
|
234
|
+
# If the challenge failed, a `400 Bad Request` JSON
|
235
|
+
# response is rendered.
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# {"error": "Please try a different passkey."}
|
239
|
+
#
|
240
|
+
# @see Warden::WebAuthn::RegistrationHelpers#verify_registration
|
241
|
+
# @see https://github.com/cedarcode/webauthn-ruby#verification-phase
|
242
|
+
# @see Warden::WebAuthn::ErrorKeyFinder#webauthn_error_key
|
109
243
|
def verify_passkey_registration_challenge
|
110
244
|
@webauthn_credential = verify_registration(relying_party: relying_party)
|
111
245
|
rescue ::WebAuthn::Error => e
|
@@ -113,12 +247,17 @@ module Devise
|
|
113
247
|
render json: { message: find_message(error_key) }, status: :bad_request
|
114
248
|
end
|
115
249
|
|
116
|
-
#
|
250
|
+
# @!visibility public
|
251
|
+
# Adds the generated WebAuthn User ID to `devise_parameter_sanitizer`'s permitted keys
|
117
252
|
def configure_sign_up_params
|
118
|
-
params[
|
253
|
+
params[resource_name][:webauthn_id] = registration_user_id
|
119
254
|
devise_parameter_sanitizer.permit(:sign_up, keys: [:webauthn_id])
|
120
255
|
end
|
121
256
|
|
257
|
+
# @!visibility public
|
258
|
+
# Prepares the user details for a WebAuthn registration request
|
259
|
+
# @see new_challenge
|
260
|
+
# @see https://github.com/cedarcode/webauthn-ruby#initiation-phase
|
122
261
|
def user_details_for_registration
|
123
262
|
store_registration_user_id
|
124
263
|
{ id: registration_user_id, name: sign_up_params[:email] }
|
@@ -8,7 +8,7 @@ module Devise
|
|
8
8
|
|
9
9
|
included do
|
10
10
|
include Warden::WebAuthn::AuthenticationInitiationHelpers
|
11
|
-
include Warden::WebAuthn::
|
11
|
+
include Warden::WebAuthn::RackHelpers
|
12
12
|
|
13
13
|
# Prepending is crucial to ensure that the relying party is set in the
|
14
14
|
# request.env before the strategy is executed
|
@@ -29,8 +29,8 @@ module Devise
|
|
29
29
|
|
30
30
|
protected
|
31
31
|
|
32
|
-
def
|
33
|
-
raise "need to define relying_party for this
|
32
|
+
def relying_party
|
33
|
+
raise NoMethodError, "need to define relying_party for this #{self.class.name}"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "controllers/concerns/
|
3
|
+
require_relative "controllers/concerns/reauthentication"
|
4
4
|
require_relative "controllers/concerns/reauthentication_challenge"
|
5
5
|
require_relative "controllers/sessions_controller_concern"
|
6
6
|
require_relative "controllers/registrations_controller_concern"
|
@@ -9,6 +9,60 @@ require_relative "controllers/passkeys_controller_concern"
|
|
9
9
|
|
10
10
|
module Devise
|
11
11
|
module Passkeys
|
12
|
+
# This module contains all the controller-level logic for:
|
13
|
+
#
|
14
|
+
# - User (resource) registration management (signup/delete account) using passkeys
|
15
|
+
# - User (resource) management of their passkeys
|
16
|
+
# - User (resource) authentication & reauthenticating using their passkeys
|
17
|
+
#
|
18
|
+
# Rather than having base classes, `Devise::Passkeys::Controllers` has a series of concerns
|
19
|
+
# that can be mixed into your app's controllers. This allows you to change behavior,
|
20
|
+
# and does not keep you stuck down a path that could be incompatible with your
|
21
|
+
# existing authentication setup.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# class Users::RegistrationsController < Devise::RegistrationsController
|
25
|
+
# include Devise::Passkeys::Controllers::RegistrationsControllerConcern
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# class Users::SessionsController < Devise::SessionsController
|
30
|
+
# include Devise::Passkeys::Controllers::SessionsControllerConcern
|
31
|
+
# # ... any custom code you need
|
32
|
+
#
|
33
|
+
# def relying_party
|
34
|
+
# WebAuthn::RelyingParty.new(...)
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # frozen_string_literal: true
|
39
|
+
#
|
40
|
+
# class Users::ReauthenticationController < DeviseController
|
41
|
+
# include Devise::Passkeys::Controllers::ReauthenticationControllerConcern
|
42
|
+
# # ... any custom code you need
|
43
|
+
#
|
44
|
+
# def relying_party
|
45
|
+
# WebAuthn::RelyingParty.new(...)
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# # frozen_string_literal: true
|
50
|
+
#
|
51
|
+
# class Users::PasskeysController < DeviseController
|
52
|
+
# include Devise::Passkeys::Controllers::PasskeysControllerConcern
|
53
|
+
# # ... any custom code you need
|
54
|
+
#
|
55
|
+
# def relying_party
|
56
|
+
# WebAuthn::RelyingParty.new(...)
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# *Note:* The `Devise::Passkeys::Controllers::Concerns` namespace is for:
|
61
|
+
# > Code, related to the concerns for controllers, that can be extracted into a standalone
|
62
|
+
# > module that can be included & extended as needed for apps that need
|
63
|
+
# > to do something custom with their setup.
|
64
|
+
# >
|
65
|
+
# > https://github.com/ruby-passkeys/devise-passkeys/issues/4#issuecomment-1590357907
|
12
66
|
module Controllers
|
13
67
|
end
|
14
68
|
end
|
@@ -2,8 +2,16 @@
|
|
2
2
|
|
3
3
|
module Devise
|
4
4
|
module Models
|
5
|
+
# This is the actual module that gets included in your
|
6
|
+
# model when you include `:passkey_authenticatable` in the
|
7
|
+
# `devise` call (eg: `devise :passkey_authenticatable, ...`).
|
5
8
|
module PasskeyAuthenticatable
|
6
|
-
|
9
|
+
# This is a callback that is called right after a successful passkey authentication.
|
10
|
+
#
|
11
|
+
# By default, it is a no-op, but you can override it in your model for any custom behavior
|
12
|
+
# (such as notifying the user of a new login).
|
13
|
+
# @param passkey [String] the passkey that was used for authentication
|
14
|
+
def after_passkey_authentication(passkey:); end
|
7
15
|
end
|
8
16
|
end
|
9
17
|
end
|
@@ -9,6 +9,13 @@ module Devise
|
|
9
9
|
def authentication_challenge_key
|
10
10
|
"#{mapping.singular}_current_reauthentication_challenge"
|
11
11
|
end
|
12
|
+
|
13
|
+
# Reauthentication runs through Authentication (user_set)
|
14
|
+
# as part of its cycle, which would normally reset CSRF
|
15
|
+
# data in the session
|
16
|
+
def clean_up_csrf?
|
17
|
+
false
|
18
|
+
end
|
12
19
|
end
|
13
20
|
end
|
14
21
|
end
|
data/lib/devise/passkeys.rb
CHANGED
@@ -11,7 +11,29 @@ require_relative "passkeys/reauthentication_strategy"
|
|
11
11
|
require_relative "passkeys/version"
|
12
12
|
|
13
13
|
module Devise
|
14
|
+
# This module provides a devise extension to use passkeys instead
|
15
|
+
# of passwords for user authentication.
|
16
|
+
#
|
17
|
+
# It is lightweight and non-configurable. It does what it has to do and
|
18
|
+
# leaves some manual implementation to you.
|
19
|
+
#
|
20
|
+
# Please consult the {file:README.md#label-Usage} for installation & configuration instructions;
|
21
|
+
# and the links below for additional reading about:
|
22
|
+
#
|
23
|
+
# - What passkeys are
|
24
|
+
# - The underlying gems used to build this devise extension
|
25
|
+
# - Platform support & user interface implementation guides
|
26
|
+
#
|
27
|
+
# @see https://webauthn.guide
|
28
|
+
# @see https://passkeys.dev
|
29
|
+
# @see https://fidoalliance.org/passkeys
|
30
|
+
# @see https://github.com/cedarcode/webauthn-ruby
|
31
|
+
# @see https://github.com/ruby-passkeys/warden-webauthn
|
14
32
|
module Passkeys
|
33
|
+
# This is a helper method that creates and returns a passkey for
|
34
|
+
# the given user (`resource`), using the provided label & `WebAuthn::Credential`
|
35
|
+
# @see PasskeyIssuer#create_and_return_passkey
|
36
|
+
# @return A saved passkey for the the given user (`resource`)
|
15
37
|
def self.create_and_return_passkey(resource:, label:, webauthn_credential:, extra_attributes: {})
|
16
38
|
PasskeyIssuer.build.create_and_return_passkey(
|
17
39
|
resource: resource,
|
@@ -22,8 +44,3 @@ module Devise
|
|
22
44
|
end
|
23
45
|
end
|
24
46
|
end
|
25
|
-
|
26
|
-
Devise.add_module :passkey_authenticatable,
|
27
|
-
model: "devise/passkeys/model",
|
28
|
-
strategy: true,
|
29
|
-
no_input: true
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise-passkeys
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Cannon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: devise
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 4.7.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 4.7.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: warden-webauthn
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.3.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.3.0
|
41
41
|
description: A Devise extension to use passkeys instead of passwords for authentication,
|
42
42
|
using warden-webauthn
|
43
43
|
email:
|
@@ -47,21 +47,24 @@ extensions: []
|
|
47
47
|
extra_rdoc_files: []
|
48
48
|
files:
|
49
49
|
- ".rubocop.yml"
|
50
|
+
- ".yardopts"
|
50
51
|
- Appraisals
|
51
52
|
- CHANGELOG.md
|
52
53
|
- CODE_OF_CONDUCT.md
|
54
|
+
- CONTRIBUTING.md
|
53
55
|
- Gemfile
|
54
56
|
- Gemfile.lock
|
55
57
|
- LICENSE.txt
|
56
58
|
- README.md
|
57
59
|
- Rakefile
|
60
|
+
- THANKS.md
|
58
61
|
- devise-passkeys.gemspec
|
59
62
|
- gemfiles/.bundle/config
|
60
63
|
- gemfiles/rails_6.gemfile
|
61
64
|
- gemfiles/rails_7.gemfile
|
62
65
|
- lib/devise/passkeys.rb
|
63
66
|
- lib/devise/passkeys/controllers.rb
|
64
|
-
- lib/devise/passkeys/controllers/concerns/
|
67
|
+
- lib/devise/passkeys/controllers/concerns/reauthentication.rb
|
65
68
|
- lib/devise/passkeys/controllers/concerns/reauthentication_challenge.rb
|
66
69
|
- lib/devise/passkeys/controllers/passkeys_controller_concern.rb
|
67
70
|
- lib/devise/passkeys/controllers/reauthentication_controller_concern.rb
|