@codenificient/passkey-auth
v1.0.0
Published
A comprehensive WebAuthn/Passkey authentication library for Next.js applications
Maintainers
Readme
@your-org/passkey-auth
A comprehensive WebAuthn/Passkey authentication library for Next.js applications. This package provides both client-side and server-side utilities for implementing secure, passwordless authentication using WebAuthn standards.
Features
- 🔐 WebAuthn/Passkey Support: Full implementation of WebAuthn standards
- 🚀 Next.js Ready: Optimized for Next.js 13+ with App Router
- 📱 Cross-Platform: Works on desktop, mobile, and tablets
- 🔒 Secure: Built-in challenge verification and origin validation
- 🎯 TypeScript: Full TypeScript support with comprehensive types
- 🧩 Modular: Use only what you need - client, server, or both
- 🔄 Database Agnostic: Works with any database via adapter pattern
Installation
npm install @your-org/passkey-auth
# or
yarn add @your-org/passkey-auth
# or
pnpm add @your-org/passkey-authQuick Start
1. Client-Side Usage
import { usePasskeyAuth } from "@your-org/passkey-auth";
function LoginPage() {
const { register, login, logout, isSupported } = usePasskeyAuth();
const handleRegister = async () => {
const result = await register("John Doe", "[email protected]");
if (result.success) {
console.log("Registration successful!");
// Redirect to dashboard
} else {
console.error("Registration failed:", result.error);
}
};
const handleLogin = async () => {
const result = await login("[email protected]");
if (result.success) {
console.log("Login successful!", result.user);
// Redirect to dashboard
} else {
console.error("Login failed:", result.error);
}
};
if (!isSupported()) {
return <div>Passkeys are not supported on this device</div>;
}
return (
<div>
<button onClick={handleRegister}>Register with Passkey</button>
<button onClick={handleLogin}>Login with Passkey</button>
<button onClick={logout}>Logout</button>
</div>
);
}2. Server-Side Usage
import { createPasskeyServer, DatabaseAdapter } from "@your-org/passkey-auth";
// Implement your database adapter
class MyDatabaseAdapter implements DatabaseAdapter {
async createUser(name: string, email: string) {
// Your database implementation
}
async getUserByEmail(email: string) {
// Your database implementation
}
// ... implement other required methods
}
// Create server instance
const passkeyServer = createPasskeyServer({
jwtSecret: process.env.JWT_SECRET!,
database: new MyDatabaseAdapter(),
rpName: "My App",
rpId: "myapp.com",
origin: "https://myapp.com",
});
// In your API routes
export async function POST(request: Request) {
const { name, email } = await request.json();
try {
const result = await passkeyServer.startRegistration(name, email);
return Response.json(result);
} catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
}API Reference
Client API
usePasskeyAuth(baseUrl?: string)
React hook for passkey authentication.
Returns:
register(name: string, email: string): Register a new userlogin(email: string): Login with existing userlogout(): Logout current userisSupported(): Check if passkeys are supported
PasskeyClient
Class-based client for non-React environments.
import { PasskeyClient } from "@your-org/passkey-auth";
const client = new PasskeyClient("https://myapp.com");
await client.register("John Doe", "[email protected]");Server API
createPasskeyServer(config: PasskeyAuthConfig)
Create a server instance for handling authentication.
Configuration:
interface PasskeyAuthConfig {
jwtSecret: string;
database: DatabaseAdapter;
rpName?: string;
rpId?: string;
origin?: string;
}Methods:
startRegistration(name: string, email: string): Start user registrationverifyRegistration(credential: WebAuthnCredential, challenge: number[]): Verify registrationstartLogin(email: string): Start user loginverifyLogin(credential: WebAuthnCredential, challenge: number[]): Verify logincreateToken(user: User): Create JWT tokenverifyToken(token: string): Verify JWT token
Database Adapter
Implement the DatabaseAdapter interface to work with your database:
interface DatabaseAdapter {
// User operations
createUser(name: string, email: string): Promise<User>;
getUserById(id: string): Promise<User | null>;
getUserByEmail(email: string): Promise<User | null>;
// Passkey operations
savePasskey(
userId: string,
credentialId: string,
publicKey: Uint8Array,
counter: number,
deviceType?: string,
backedUp?: boolean,
transports?: string[]
): Promise<void>;
getPasskeyByCredentialId(credentialId: string): Promise<Passkey | null>;
updatePasskeyCounter(credentialId: string, counter: number): Promise<void>;
// Challenge operations
saveChallenge(challenge: string, userId?: string): Promise<void>;
getChallenge(challenge: string): Promise<{ userId?: string } | null>;
deleteChallenge(challenge: string): Promise<void>;
}Complete Next.js Example
1. Install Dependencies
npm install @your-org/passkey-auth jose2. Create Database Adapter
// lib/database-adapter.ts
import {
DatabaseAdapter,
User,
Passkey,
AuthChallenge,
} from "@your-org/passkey-auth";
export class PrismaDatabaseAdapter implements DatabaseAdapter {
// Implement all required methods using your ORM
async createUser(name: string, email: string): Promise<User> {
// Your implementation
}
// ... other methods
}3. Create API Routes
// app/api/auth/register/route.ts
import { createPasskeyServer } from "@your-org/passkey-auth";
import { PrismaDatabaseAdapter } from "@/lib/database-adapter";
const passkeyServer = createPasskeyServer({
jwtSecret: process.env.JWT_SECRET!,
database: new PrismaDatabaseAdapter(),
rpName: "My App",
rpId: process.env.RP_ID || "localhost",
origin: process.env.ORIGIN || "http://localhost:3000",
});
export async function POST(request: Request) {
const { name, email } = await request.json();
try {
const result = await passkeyServer.startRegistration(name, email);
return Response.json(result);
} catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
}4. Create Client Component
// components/auth-form.tsx
"use client";
import { usePasskeyAuth } from "@your-org/passkey-auth";
import { useState } from "react";
export function AuthForm() {
const { register, login, isSupported } = usePasskeyAuth();
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [isLogin, setIsLogin] = useState(true);
const handleAuth = async () => {
if (isLogin) {
const result = await login(email);
if (result.success) {
window.location.href = "/dashboard";
}
} else {
const result = await register(name, email);
if (result.success) {
window.location.href = "/dashboard";
}
}
};
if (!isSupported()) {
return <div>Passkeys are not supported on this device</div>;
}
return (
<form
onSubmit={(e) => {
e.preventDefault();
handleAuth();
}}
>
{!isLogin && (
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
)}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type="submit">{isLogin ? "Login" : "Register"}</button>
<button type="button" onClick={() => setIsLogin(!isLogin)}>
{isLogin ? "Need an account?" : "Have an account?"}
</button>
</form>
);
}Security Considerations
- JWT Secret: Use a strong, random JWT secret
- HTTPS: Always use HTTPS in production
- Origin Validation: Ensure origin validation is properly configured
- Challenge Expiry: Challenges expire after 5 minutes by default
- Database Security: Secure your database and use proper encryption for sensitive data
Browser Support
- Chrome 67+
- Firefox 60+
- Safari 14+
- Edge 79+
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE file for details.
