Nuxt Starter Kit includes built-in support for passkey authentication, providing a modern, secure, and user-friendly way to authenticate users without passwords.
Passkeys are a modern authentication standard based on public-key cryptography. Instead of remembering and typing passwords, users can authenticate using their device's biometric sensors (Face ID, Touch ID, Windows Hello) or screen lock.
More Secure
Resistant to phishing, credential stuffing, and password-related attacks. Private keys never leave the user's device.
Faster & Simpler
No passwords to remember or type. Authentication happens in seconds with a simple biometric scan or device unlock.
Cross-Device Support
Passkeys can be synced securely across devices through platform mechanisms like iCloud Keychain or Google Password Manager.
Passkeys use WebAuthn (Web Authentication API), a W3C standard:
/auth/passkey for a dedicated passkey authentication page.Passkeys are supported in all modern browsers:
The passkey implementation consists of three main parts:
layers/auth/app/components/passkey/)PasskeyRegister.vue - Registration formPasskeyLogin.vue - Authentication buttonPasskeyAuth.vue - Combined auth wrapperlayers/auth/server/api/webauthn/)register.post.ts - Handles passkey registrationauthenticate.post.ts - Handles passkey authenticationlayers/db/server/utils/)schema.ts - Credentials table schemauseCredentialDb.ts - Database operations for credentialsPasskey credentials are stored in the credentials table:
{
userId: string // Foreign key to users table
id: string // Credential ID (unique)
publicKey: string // Public key for verification
counter: number // Signature counter (replay protection)
backedUp: boolean // Whether credential is backed up
transports: string // Supported transports (USB, NFC, etc.)
}
/api/webauthn/registerHandles new passkey creation:
// Client-side
const { register } = useWebAuthn()
await register({ userName: 'user@example.com' })
/api/webauthn/authenticateHandles passkey sign-in:
// Client-side
const { authenticate } = useWebAuthn()
await authenticate() // Can optionally pass userName to skip selection
WebAuthn is enabled in layers/auth/nuxt.config.ts:
export default defineNuxtConfig({
auth: {
webAuthn: true,
},
modules: ['nuxt-auth-utils'],
})
emailVerified set to true by defaultAdd passkey authentication to your own auth page:
<script setup lang="ts">
const { authenticate } = useWebAuthn()
const { fetch: fetchUserSession } = useUserSession()
async function handlePasskeyAuth () {
await authenticate()
await fetchUserSession()
// User is now authenticated!
}
</script>
<template>
<UButton
icon="i-lucide-fingerprint"
@click="handlePasskeyAuth"
>
Sign in with Passkey
</UButton>
</template>
@simplewebauthn/server@11 - Server-side WebAuthn library@simplewebauthn/browser@11 - Client-side WebAuthn librarynuxt-auth-utils - Nuxt authentication utilities with WebAuthn supportThe credentials table migration is included in the database migrations. Run:
pnpm db:generate # Already generated
Migrations are automatically applied in development mode.
The useWebAuthn() composable is auto-imported from nuxt-auth-utils and provides:
register(options) - Create a new passkeyauthenticate(userName?) - Authenticate with an existing passkey