You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -226,7 +228,7 @@ It can also be set using environment variables:
226
228
227
229
You can add your favorite provider by creating a new file in [src/runtime/server/lib/oauth/](./src/runtime/server/lib/oauth/).
228
230
229
-
### Example
231
+
####Example
230
232
231
233
Example: `~/server/routes/auth/github.get.ts`
232
234
@@ -255,9 +257,9 @@ Make sure to set the callback URL in your OAuth app settings as `<your-domain>/a
255
257
256
258
If the redirect URL mismatch in production, this means that the module cannot guess the right redirect URL. You can set the `NUXT_OAUTH_<PROVIDER>_REDIRECT_URL` env variable to overwrite the default one.
257
259
258
-
### Password Utils
260
+
### Password Hashing
259
261
260
-
Nuxt Auth Utils provides a `hashPassword` and `verifyPassword` function to hash and verify passwords by using [scrypt](https://en.wikipedia.org/wiki/Scrypt) as it is supported in many JS runtime.
262
+
Nuxt Auth Utils provides password hashing utilities like `hashPassword` and `verifyPassword` to hash and verify passwords by using [scrypt](https://en.wikipedia.org/wiki/Scrypt) as it is supported in many JS runtime.
WebAuthn (Web Authentication) is a web standard that enhances security by replacing passwords with passkeys using public key cryptography. Users can authenticate with biometric data (like fingerprints or facial recognition) or physical devices (like USB keys), reducing the risk of phishing and password breaches. This approach offers a more secure and user-friendly authentication method, supported by major browsers and platforms.
290
+
291
+
To enable WebAuthn you need to:
292
+
293
+
1. Install the peer dependencies:
294
+
295
+
```bash
296
+
npx nypm i @simplewebauthn/server @simplewebauthn/browser
297
+
```
298
+
299
+
2. Enable it in your `nuxt.config.ts`
300
+
301
+
```ts
302
+
exportdefaultdefineNuxtConfig({
303
+
auth: {
304
+
webAuthn: true
305
+
}
306
+
})
307
+
```
308
+
309
+
#### Example
310
+
311
+
In this example we will implement the very basic steps to register and authenticate a credential.
312
+
313
+
The full code can be found in the [playground](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn). The example uses a SQLite database with the following minimal tables:
314
+
315
+
```sql
316
+
CREATETABLEusers (
317
+
id INTEGERPRIMARY KEY AUTOINCREMENT,
318
+
email TEXTNOT NULL
319
+
);
320
+
321
+
CREATETABLEIF NOT EXISTS credentials (
322
+
userId INTEGERNOT NULLREFERENCES users(id) ON DELETE CASCADEONUPDATE CASCADE,
323
+
id TEXT UNIQUE NOT NULL,
324
+
publicKey TEXTNOT NULL,
325
+
counter INTEGERNOT NULL,
326
+
backedUp INTEGERNOT NULL,
327
+
transports TEXTNOT NULL,
328
+
PRIMARY KEY ("userId", "id")
329
+
);
330
+
```
331
+
332
+
- For the `users` table it is important to have a unique identifier such as a username or email (here we use email). When creating a new credential, this identifier is required and stored with the passkey on the user's device, password manager, or authenticator.
333
+
- The `credentials` table stores:
334
+
- The `userId` from the `users` table.
335
+
- The credential `id` (as unique index)
336
+
- The credential `publicKey`
337
+
- A `counter`. Each time a credential is used, the counter is incremented. We can use this value to perform extra security checks. More about `counter` can be read [here](https://simplewebauthn.dev/docs/packages/server#3-post-registration-responsibilities). For this example, we won't be using the counter. But you should update the counter in your database with the new value.
338
+
- A `backedUp` flag. Normally, credentials are stored on the generating device. When you use a password manager or authenticator, the credential is "backed up" because it can be used on multiple devices. See [this section](https://arc.net/l/quote/ugaemxot) for more details.
339
+
- The credential `transports`. It is an array of strings that indicate how the credential communicates with the client. It is used to show the correct UI for the user to utilize the credential. Again, see [this section](https://arc.net/l/quote/ycxtiorp) for more details.
340
+
341
+
The following code does not include the actual database queries, but shows the general steps to follow. The full example can be found in the playground: [registration](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn/register.post.ts), [authentication](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/api/webauthn/authenticate.post.ts) and the [database setup](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/server/plugins/database.ts).
342
+
343
+
```ts
344
+
// server/api/webauthn/register.post.ts
345
+
import { z } from'zod'
346
+
exportdefaultdefineWebAuthnRegisterEventHandler({
347
+
// optional
348
+
validateUser: z.object({
349
+
// we want the userName to be a valid email
350
+
userName: z.string().email()
351
+
}).parse,
352
+
async onSuccess(event, { credential, user }) {
353
+
// The credential creation has been successful
354
+
// We need to create a user if it does not exist
355
+
const db =useDatabase()
356
+
357
+
// Get the user from the database
358
+
let dbUser =awaitdb.sql`...`
359
+
if (!dbUser) {
360
+
// Store new user in database & its credentials
361
+
dbUser=awaitdb.sql`...`
362
+
}
363
+
364
+
// we now need to store the credential in our database and link it to the user
// The credential authentication has been successful
406
+
// We can look it up in our database and get the corresponding user
407
+
const db =useDatabase()
408
+
const user =awaitdb.sql`...`
409
+
410
+
// Update the counter in the database (authenticationInfo.newCounter)
411
+
awaitdb.sql`...`
412
+
413
+
// Set the user session
414
+
awaitsetUserSession(event, {
415
+
user: {
416
+
id: user.id
417
+
},
418
+
loggedInAt: Date.now(),
419
+
})
420
+
},
421
+
})
422
+
```
423
+
424
+
> [!IMPORTANT]
425
+
> By default, the webauthn event handlers will store the challenge in a short lived, encrypted session cookie. This is not recommended for applications that require strong security guarantees. On a secure connection (https) it is highly unlikely for this to cause problems. However, if the connection is not secure, there is a possibility of a man-in-the-middle attack. To prevent this, you should use a database or KV store to store the challenge instead. For this the `storeChallenge` and `getChallenge` functions are provided.
.then(fetchUserSession) // refetch the user session
464
+
}
465
+
466
+
asyncfunction signIn() {
467
+
awaitauthenticate(userName.value)
468
+
.then(fetchUserSession) // refetch the user session
469
+
}
470
+
</script>
471
+
472
+
<template>
473
+
<form @submit.prevent="signUp">
474
+
<inputv-model="userName"placeholder="Email or username"/>
475
+
<buttontype="submit">Signup</button>
476
+
</form>
477
+
<form @submit.prevent="signIn">
478
+
<inputv-model="userName"placeholder="Email or username"/>
479
+
<buttontype="submit">Signin</button>
480
+
</form>
481
+
</template>
482
+
```
483
+
484
+
Take a look at the [`WebAuthnModal.vue`](https://github.com/atinux/nuxt-auth-utils/blob/main/playground/components/WebAuthnModal.vue) for a full example.
485
+
486
+
#### Demo
487
+
488
+
A full demo can be found on https://todo-passkeys.nuxt.dev using [Drizzle ORM](https://orm.drizzle.team/) and [NuxtHub](https://hub.nuxt.com).
489
+
490
+
The source code of the demo is available on https://github.com/atinux/todo-passkeys.
0 commit comments