@@ -22,11 +22,12 @@ def create_passkey_for(account, fake_client)
2222 )
2323 end
2424
25- def generate_assertion ( fake_client , challenge :, credential :)
25+ def generate_assertion ( fake_client , challenge :, credential :, user_handle : nil )
2626 fake_client . get (
2727 challenge : challenge ,
2828 allow_credentials : [ credential . external_id ] ,
29- user_verified : true
29+ user_verified : true ,
30+ user_handle : user_handle
3031 )
3132 end
3233
@@ -43,7 +44,8 @@ def generate_assertion(fake_client, challenge:, credential:)
4344 assertion = generate_assertion (
4445 client ,
4546 challenge : session [ :authentication_challenge ] ,
46- credential : passkey
47+ credential : passkey ,
48+ user_handle : WebAuthn . configuration . encoder . decode ( user . webauthn_id )
4749 )
4850
4951 expect do
@@ -64,7 +66,8 @@ def generate_assertion(fake_client, challenge:, credential:)
6466 assertion = generate_assertion (
6567 client ,
6668 challenge : session [ :authentication_challenge ] ,
67- credential : passkey
69+ credential : passkey ,
70+ user_handle : WebAuthn . configuration . encoder . decode ( user . webauthn_id )
6871 )
6972 passkey . destroy!
7073
@@ -83,7 +86,8 @@ def generate_assertion(fake_client, challenge:, credential:)
8386
8487 assertion = client . get (
8588 challenge : WebAuthn . configuration . encoder . encode ( "invalid_challenge" ) ,
86- allow_credentials : [ passkey . external_id ]
89+ allow_credentials : [ passkey . external_id ] ,
90+ user_handle : WebAuthn . configuration . encoder . decode ( user . webauthn_id )
8791 )
8892
8993 post account_session_path , params : {
@@ -96,6 +100,45 @@ def generate_assertion(fake_client, challenge:, credential:)
96100 expect ( controller . current_account ) . to be_nil
97101 end
98102
103+ it "rejects sign-in when userHandle is missing from the response" do
104+ get new_account_session_path
105+
106+ assertion = client . get (
107+ challenge : session [ :authentication_challenge ] ,
108+ allow_credentials : [ passkey . external_id ] ,
109+ user_verified : true
110+ )
111+
112+ post account_session_path , params : {
113+ public_key_credential : assertion . to_json
114+ }
115+
116+ expect ( response ) . to have_http_status ( :unprocessable_entity )
117+ expect ( flash [ :alert ] ) . to eq ( I18n . t ( "devise.failure.passkey_not_found" ) )
118+ expect ( controller . current_account ) . to be_nil
119+ end
120+
121+ it "rejects sign-in when userHandle does not match the passkey owner" do
122+ other_user = Account . create! ( email : "other@example.com" , password : password )
123+
124+ get new_account_session_path
125+
126+ assertion = client . get (
127+ challenge : session [ :authentication_challenge ] ,
128+ allow_credentials : [ passkey . external_id ] ,
129+ user_verified : true ,
130+ user_handle : WebAuthn . configuration . encoder . decode ( other_user . webauthn_id )
131+ )
132+
133+ post account_session_path , params : {
134+ public_key_credential : assertion . to_json
135+ }
136+
137+ expect ( response ) . to have_http_status ( :unprocessable_entity )
138+ expect ( flash [ :alert ] ) . to eq ( I18n . t ( "devise.failure.passkey_not_found" ) )
139+ expect ( controller . current_account ) . to be_nil
140+ end
141+
99142 it "fails when credential param is missing" do
100143 post account_session_path , params : { }
101144
0 commit comments