# Convex Passkey-auth Passwordless WebAuthn passkey authentication — self-minted JWTs, multi-device support, session management, and React hooks for Convex. This is an upstream package. It has not been adapted or qualified for Bijection. - Package: convex-passkey-auth - Source: https://github.com/TimpiaAI/convex-passkey-auth - npm: https://www.npmjs.com/package/convex-passkey-auth - Directory: https://bijection.com/components/convex-passkey-auth Read the upstream README for setup, license, and compatibility before using it. ## Upstream package documentation Recorded 2026-10-23. This describes convex-passkey-auth; check the Bijection adaptation separately. This component provides complete WebAuthn passkey authentication for Convex apps, handling the full challenge/response flow with self-minted HMAC-SHA256 JWTs. It includes multi-device support, session management, and React hooks for registration and login. The component eliminates passwords entirely while providing secure session tokens compatible with Convex's auth system. ### Use cases • **Replace traditional password auth** with WebAuthn passkeys for better security and user experience • **Build multi-device authentication** where users can authenticate from phones, laptops, and hardware security keys • **Implement passwordless onboarding** with passkey registration and automatic user creation • **Add secure session management** with configurable expiry, refresh tokens, and logout capabilities • **Deploy preview environments** without hardcoded origins since rpId is derived from the client ### How it works The component provides a `PasskeyAuth` class that handles WebAuthn challenge generation and verification through methods like `generateRegistrationOptions` and `verifyAuthentication`. You create Convex mutations that wrap these methods, then use the provided React hooks (`usePasskeyRegister`, `usePasskeyLogin`, `usePasskeyAuth`) to handle the browser WebAuthn APIs. Session tokens are HMAC-SHA256 signed JWTs that integrate directly with Convex's auth system. The `validateSession` method checks token validity and returns user data, while `getOrCreateUser` provides stable user identifiers from passkey credentials. The component stores passkeys, sessions, and challenges in Convex tables with automatic cleanup via cron jobs. ### Upstream README # convex-passkey-auth A [Convex](https://convex.dev) component for passwordless WebAuthn passkey authentication with self-minted JWTs, multi-device support, session management, and React hooks. ## Features - **Passkey registration** - Register new passkeys and associate them with user identifiers - **Challenge/response authentication** - Full WebAuthn challenge/response flow via browser APIs - **Self-minted JWTs** - HMAC-SHA256 signed session tokens compatible with Convex auth - **Session management** - Configurable expiry and automatic refresh strategy - **Multi-device support** - Multiple passkeys per user (phone, laptop, security key) - **`getOrCreateUser` helper** - Stable user identifier from passkey ID - **Preview deployment support** - No hardcoded origins; rpId derived from client - **React hooks** - `usePasskeyRegister`, `usePasskeyLogin`, `usePasskeyAuth` - **Server-side validation** - Validate sessions and get current user from any Convex function - **Session invalidation** - Logout (single session) and logout-all (force everywhere) - **Passkey revocation** - Revoke individual passkeys when devices are lost ## Installation ```bash npm install convex-passkey-auth ``` ## Setup ### 1. Add the component to your Convex app ```ts // convex/convex.config.ts import { defineApp } from "convex/server"; import passkeyAuth from "convex-passkey-auth/convex.config"; const app = defineApp(); app.use(passkeyAuth); export default app; ``` ### 2. Create server-side helpers ```ts // convex/auth.ts import { PasskeyAuth } from "convex-passkey-auth"; import { components } from "./_generated/api"; export const passkeyAuth = new PasskeyAuth(components.passkeyAuth, { rpName: "My App", sessionExpiryMs: 30 * 24 * 60 * 60 * 1000, // 30 days refreshAfterMs: 24 * 60 * 60 * 1000, // refresh daily }); ``` ### 3. Create Convex mutations for the client ```ts // convex/passkeys.ts import { mutation } from "./_generated/server"; import { v } from "convex/values"; import { passkeyAuth } from "./auth"; export const generateRegistrationChallenge = mutation({ args: { identifier: v.string(), displayName: v.optional(v.string()) }, handler: async (ctx, args) => { return await passkeyAuth.generateRegistrationOptions(ctx, args); }, }); export const verifyRegistration = mutation({ args: { identifier: v.string(), credentialId: v.string(), publicKey: v.string(), challenge: v.string(), counter: v.number(), deviceName: v.optional(v.string()), displayName: v.optional(v.string()), }, handler: async (ctx, args) => { return await passkeyAuth.verifyRegistration(ctx, args); }, }); export const generateAuthChallenge = mutation({ args: { identifier: v.optional(v.string()) }, handler: async (ctx, args) => { return await passkeyAuth.generateAuthenticationOptions(ctx, args); }, }); export const verifyAuth = mutation({ args: { credentialId: v.string(), challenge: v.string(), counter: v.number(), }, handler: async (ctx, args) => { return await passkeyAuth.verifyAuthentication(ctx, args); }, }); export const validateSession = mutation({ args: { tokenHash: v.string() }, handler: async (ctx, args) => { return await passkeyAuth.validateSession(ctx, args.tokenHash); }, }); export const logout = mutation({ args: { tokenHash: v.string() }, handler: async (ctx, args) => { return await passkeyAuth.logout(ctx, args.tokenHash); }, }); ``` ### 4. Use React hooks in your app ```tsx // src/App.tsx import { usePasskeyRegister, usePasskeyLogin, usePasskeyAuth } from "convex-passkey-auth/react"; import { useMutation } from "convex/react"; import { api } from "../convex/_generated/api"; function App() { const generateRegChallenge = useMutation(api.passkeys.generateRegistrationChallenge); const verifyReg = useMutation(api.passkeys.verifyRegistration); const generateAuthChallenge = useMutation(api.passkeys.generateAuthChallenge); const verifyAuth = useMutation(api.passkeys.verifyAuth); const validateSessionMutation = useMutation(api.passkeys.validateSession); const logoutMutation = useMutation(api.passkeys.logout); const { register, isRegistering } = usePasskeyRegister({ generateChallenge: generateRegChallenge, verifyRegistration: verifyReg, rpName: "My App", }); const { login, isLoggingIn } = usePasskeyLogin({ generateChallenge: generateAuthChallenge, verifyAuthentication: verifyAuth, }); const { user, isAuthenticated, isLoading, logout } = usePasskeyAuth({ validateSession: validateSessionMutation, invalidateSession: logoutMutation, }); if (isLoading) return
Welcome, {user?.userId}