devise-passkeys 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +40 -2
- data/CONTRIBUTING.md +47 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -38
- data/README.md +25 -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 -0
- 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,
|
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.2.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-07-08 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.2.
|
33
|
+
version: 0.2.1
|
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.2.
|
40
|
+
version: 0.2.1
|
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
|