Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Convex Passkey-auth

Passwordless WebAuthn passkey authentication — self-minted JWTs, multi-device support, session management, and React hooks for Convex.

Upstream source
git clone https://github.com/TimpiaAI/convex-passkey-auth.git
cd convex-passkey-auth

This 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.

Available to evaluate

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. 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.

agent-prompt.md
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)
  • 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

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)

MethodDescription
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

HookReturns
usePasskeyRegister(options){ register, isRegistering, error }
usePasskeyLogin(options){ login, isLoggingIn, error }
usePasskeyAuth(options){ user, isAuthenticated, isLoading, logout }

Utilities

FunctionDescription
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

Keywords