Convex Passkey-auth
Passwordless WebAuthn passkey authentication — self-minted JWTs, multi-device support, session management, and React hooks for Convex.
git clone https://github.com/TimpiaAI/convex-passkey-auth.git
cd convex-passkey-authThis upstream package has not been adapted or qualified for Bijection. Read the original documentation before adopting it.
Description
Passwordless WebAuthn passkey authentication — self-minted JWTs, multi-device support, session management, and React hooks for Convex.
Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- Exercise the component in your deployment according to your project’s verification rules.
Open source
Maintained source: TimpiaAI/convex-passkey-auth. License: See repository. Consult the repository for license terms, notices, and contributor attribution.
From the upstream publisher
Convex component for passwordless WebAuthn passkey authentication — self-minted JWTs, multi-device support, session management, and React hooks
Explore the demo ↗Upstream overview
From the upstream package documentation.
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
From the upstream package documentation.
- 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
From the upstream package documentation.
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 package FAQ
Does convex-passkey-auth work with existing Convex authentication?
convex-passkey-auth operates independently of Convex's built-in auth system. It generates its own JWTs and manages sessions through Convex functions, allowing you to implement passkey authentication without conflicts with other auth methods.
What browsers and devices support WebAuthn passkeys?
WebAuthn passkeys work in all modern browsers including Chrome, Firefox, Safari, and Edge. Users can authenticate with built-in biometrics (Face ID, Touch ID, Windows Hello), external security keys, or cross-device authentication from their phones.
How does session management work with convex-passkey-auth?
The component includes configurable JWT expiration and refresh token handling through Convex functions. Sessions are stored in your Convex database with automatic cleanup, and React hooks provide real-time authentication state updates.
Can users register multiple passkeys for the same account?
Yes, convex-passkey-auth supports multi-device registration where users can add passkeys from different devices. The component stores multiple credential IDs per user, enabling seamless authentication across phones, laptops, and hardware security keys.
Use with agents and CLI
Give your coding agent the component’s source identity and setup guidance.
Help me integrate Convex Passkey-auth with Bijection.
Read: https://bijection.com/components/convex-passkey-auth/SKILL.md
Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.Upstream README.md
View repository ↗Documentation from TimpiaAI/convex-passkey-auth, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.
convex-passkey-auth
A Convex 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)
getOrCreateUserhelper - 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
npm install convex-passkey-auth
Setup
1. Add the component to your Convex app
// 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
// 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
// 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
// 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 <div>Loading...</div>;
if (isAuthenticated) {
return (
<div>
<p>Welcome, {user?.userId}</p>
<button onClick={logout}>Logout</button>
</div>
);
}
return (
<div>
<button
onClick={() => register("user@example.com", "John Doe")}
disabled={isRegistering}
>
{isRegistering ? "Registering..." : "Register Passkey"}
</button>
<button
onClick={() => login("user@example.com")}
disabled={isLoggingIn}
>
{isLoggingIn ? "Logging in..." : "Login with Passkey"}
</button>
</div>
);
}
API Reference
Server-side (PasskeyAuth class)
| Method | Description |
|---|---|
generateRegistrationOptions(ctx, { identifier, displayName? }) | Generate WebAuthn registration challenge |
verifyRegistration(ctx, { identifier, credentialId, publicKey, challenge, counter }) | Verify registration and store passkey |
generateAuthenticationOptions(ctx, { identifier? }) | Generate WebAuthn authentication challenge |
verifyAuthentication(ctx, { credentialId, challenge, counter }) | Verify authentication and create session |
validateSession(ctx, tokenHash) | Validate a session token |
logout(ctx, tokenHash) | Invalidate a single session |
logoutAll(ctx, userId) | Invalidate all sessions for a user |
getOrCreateUser(ctx, { identifier, displayName? }) | Find or create user by identifier |
getUser(ctx, userId) | Get user info |
listPasskeys(ctx, userId) | List passkeys for a user |
revokePasskey(ctx, credentialId) | Revoke a specific passkey |
cleanupExpiredSessions(ctx) | Delete expired sessions (for cron) |
cleanupExpiredChallenges(ctx) | Delete expired challenges (for cron) |
React hooks
| Hook | Returns |
|---|---|
usePasskeyRegister(options) | { register, isRegistering, error } |
usePasskeyLogin(options) | { login, isLoggingIn, error } |
usePasskeyAuth(options) | { user, isAuthenticated, isLoading, logout } |
Utilities
| Function | Description |
|---|---|
hashSessionToken(token) | Hash a raw session token for server-side validation |
Cron Jobs
Set up cleanup crons to keep the database tidy:
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.daily("cleanup sessions", { hourUTC: 3, minuteUTC: 0 }, internal.cleanup.expiredSessions);
crons.hourly("cleanup challenges", { minuteUTC: 30 }, internal.cleanup.expiredChallenges);
export default crons;
License
MIT