|
176 | 176 |
|
177 | 177 | options = WebAuthn::Credential.options_for_create( |
178 | 178 | user: { id: user.webauthn_id, name: user.name }, |
179 | | - exclude: user.credentials.map { |c| c.webauthn_id } |
| 179 | + exclude: user.credentials.map { |c| c.webauthn_id }, |
| 180 | + authenticator_selection: { |
| 181 | + resident_key: "discouraged", # For a passwordless login or 2FA. Use "required" for a passkey-based (passwordless and usernameless) login. |
| 182 | + user_verification: "required" # For a passwordless or passkey-based (passwordless and usernameless) login. Use "discouraged" for 2FA. |
| 183 | + } |
180 | 184 | ) |
181 | 185 |
|
182 | 186 | # Store the newly generated challenge somewhere so you can have it |
@@ -204,7 +208,8 @@ session[:creation_challenge] = options.challenge |
204 | 208 | webauthn_credential = WebAuthn::Credential.from_create(params[:publicKeyCredential]) |
205 | 209 |
|
206 | 210 | begin |
207 | | - webauthn_credential.verify(session[:creation_challenge]) |
| 211 | + # Enforce user verification (pairs with the "required" request above). Omit for 2FA. |
| 212 | + webauthn_credential.verify(session[:creation_challenge], user_verification: true) |
208 | 213 |
|
209 | 214 | # Store Credential ID, Credential Public Key and Sign Count for future authentications |
210 | 215 | user.credentials.create!( |
|
224 | 229 | #### Initiation phase |
225 | 230 |
|
226 | 231 | ```ruby |
227 | | -options = WebAuthn::Credential.options_for_get(allow: user.credentials.map { |c| c.webauthn_id }) |
| 232 | +options = WebAuthn::Credential.options_for_get( |
| 233 | + # Pass `allow` when the user is already known (passwordless/2FA/reauth). |
| 234 | + # For a passkey-based (usernameless and passwordless) login, omit it and resolve the user from `user_handle` after `from_get`. |
| 235 | + allow: user.credentials.map { |c| c.webauthn_id }, |
| 236 | + user_verification: "required" # For a passwordless or passkey-based (passwordless and usernameless) login. Use "discouraged" for 2FA. |
| 237 | +) |
228 | 238 |
|
229 | 239 | # Store the newly generated challenge somewhere so you can have it |
230 | 240 | # for the verification phase. |
@@ -260,7 +270,8 @@ begin |
260 | 270 | webauthn_credential.verify( |
261 | 271 | session[:authentication_challenge], |
262 | 272 | public_key: stored_credential.public_key, |
263 | | - sign_count: stored_credential.sign_count |
| 273 | + sign_count: stored_credential.sign_count, |
| 274 | + user_verification: true # For a passwordless or passkey-based (passwordless and usernameless) login. Omit for 2FA. |
264 | 275 | ) |
265 | 276 |
|
266 | 277 | # Update the stored credential sign count with the value from `webauthn_credential.sign_count` |
@@ -290,7 +301,11 @@ Extensions can be requested in the initiation phase in both Credential Registrat |
290 | 301 | creation_options = WebAuthn::Credential.options_for_create( |
291 | 302 | user: { id: user.webauthn_id, name: user.name }, |
292 | 303 | exclude: user.credentials.map { |c| c.webauthn_id }, |
293 | | - extensions: { appidExclude: domain.to_s } |
| 304 | + extensions: { appidExclude: domain.to_s }, |
| 305 | + authenticator_selection: { |
| 306 | + resident_key: "discouraged", # For a passwordless login or 2FA. Use "required" for a passkey-based (passwordless and usernameless) login. |
| 307 | + user_verification: "required" # For a passwordless or passkey-based (passwordless and usernameless) login. Use "discouraged" for 2FA. |
| 308 | + } |
294 | 309 | ) |
295 | 310 |
|
296 | 311 | # OR |
@@ -344,7 +359,7 @@ to be used in the client-side code to call `navigator.credentials.create({ "publ |
344 | 359 |
|
345 | 360 | ```ruby |
346 | 361 | creation_options = WebAuthn::Credential.options_for_create( |
347 | | - user: { id: user.webauthn_id, name: user.name } |
| 362 | + user: { id: user.webauthn_id, name: user.name }, |
348 | 363 | exclude: user.credentials.map { |c| c.webauthn_id } |
349 | 364 | ) |
350 | 365 |
|
|
0 commit comments