Skip to content

Conversation

@Muhammad-Altabba
Copy link

@Muhammad-Altabba Muhammad-Altabba commented Nov 7, 2025

Implementation (or possible reference implementation) for https://github.com/nucypher/sprints/issues/273
Note: It is made with intensive usage of an LLM (Sonnet 4.5).

[UPDATE]
The code has been refactored at 1ebde9b so the JWT signing is detached from the passkey attestation. So the flow is to register JWT keys once and sign multiple JWTs without passkey interaction.


[INITIAL PR DESCRIPTION]

🔐 Passkey-Attested JWT Signing with Detached Signature Architecture

Summary

This PR implements a novel approach to JWT signing that combines the security benefits of hardware-backed passkeys with the efficiency and interoperability of standard JWTs. The key innovation is a detached signature architecture where passkeys attest JWT signing keys once during registration, enabling subsequent JWT signing operations without requiring passkey interaction.

Problem

Traditional JWT signing approaches face a trade-off:

  • Standard JWTs: Fast and interoperable, but no hardware-backed security
  • WebAuthn-only: Hardware-backed security, but custom verification and poor UX (prompts for every signature)

Solution

This implementation introduces a two-phase approach:

Phase 1: Registration (One-Time)

  • Generate an EdDSA key pair for JWT signing
  • Use passkey to sign and attest the JWT public key's fingerprint
  • Store the JWT public key, attestation, and their relationship in a database

Phase 2: Signing (Unlimited)

  • Sign JWTs using the EdDSA private key with standard jose library
  • No passkey interaction required
  • Produces clean, standard-compliant JWTs

Key Features

Register once, sign many - After one-time registration, sign unlimited JWTs without passkey prompts
Standard JWT format - Full compatibility with jose.jwtVerify() and other JWT libraries
Hardware-backed security - JWT signing keys are cryptographically attested by passkeys
Efficient verification - Two-stage process: standard JWT verify + DB authorization lookup
Clean separation - Passkey attestation stored separately, not embedded in JWT payload

Architecture

Registration Flow:
1. Generate EdDSA key pair (JWT signing key)
2. Passkey signs public key fingerprint (SHA-256)
3. Store: public key (JWK) + fingerprint + attestation → Database

Signing Flow:
1. Create JWT payload
2. Sign with EdDSA private key using jose.SignJWT()
3. Include 'kid' in header for key lookup

Verification Flow:
1. Extract 'kid' from JWT header
2. Lookup JWT public key in database
3. Verify JWT signature (standard jose.jwtVerify)
4. Confirm key is authorized by checking passkey attestation

Database Schema

Three core tables establish the trust chain:

  • credentials - Passkey credentials (WebAuthn)
  • jwt_keys - JWT signing keys with passkey attestation (1:1 with passkeys)
  • signatures - Audit trail of signed JWTs

API Endpoints

Endpoint Purpose
POST /api/jwt-keys/register Register JWT key with passkey
GET /api/jwt-keys/:keyId Retrieve JWT key information
POST /api/sign Save signed JWT to database
POST /api/validate Verify JWT (two-stage verification)

Testing

44 comprehensive tests covering:

  • JWT key generation and registration (13 tests)
  • Standard JWT signing and verification (12 tests)
  • Algorithm compatibility across ES256, RS256, EdDSA (19 tests)
  • Authorization and security checks
npm test  # All tests passing ✅

Tech Stack

  • jose v5.9.6 - Industry-standard JWT operations
  • @simplewebauthn/server v13.1.x - WebAuthn implementation
  • better-sqlite3 - Persistent storage
  • Next.js 15.3.4 - Web framework
  • Jest 29.7.0 - Testing

Security Properties

From JWT Signing:

  • Signature integrity (EdDSA)
  • Standard algorithm (interoperable)
  • Fast verification

From Passkey Attestation:

  • Hardware-backed trust
  • Origin verification
  • User presence confirmation
  • Non-repudiation

From Architecture:

  • Authorization tracking via database
  • Independent key revocation capability
  • Complete audit trail

Usage Example

// 1. Register JWT key (one-time, with passkey)
const { keyId } = await registerJWTKey(credentialId);

// 2. Sign JWTs (unlimited, no passkey needed)
const jwt = await new SignJWT({ message: "data" })
  .setProtectedHeader({ alg: "EdDSA", typ: "JWT", kid: keyId })
  .sign(privateKey);

// 3. Verify JWT (standard library)
const { payload } = await jwtVerify(jwt, publicKey);
// + check authorization in database

Files Changed

New Core Libraries:

  • src/lib/jwt-key-registration.ts - Key generation and fingerprinting
  • src/lib/jwt-detached-verifier.ts - Two-stage verification logic

API Routes:

  • src/app/api/jwt-keys/register/route.ts - Key registration endpoint
  • src/app/api/jwt-keys/[id]/route.ts - Key lookup endpoint
  • Updated src/app/api/sign/route.ts and src/app/api/validate/route.ts

Database:

  • src/lib/database.ts - Added jwt_keys table and operations

Tests:

  • src/__tests__/detached-signature.test.ts - Complete architecture testing

Documentation:

  • README.md - Comprehensive documentation
  • FLOW.md - Detailed flow diagrams
  • JWT-VERIFICATION-GUIDE.md - Integration guide

This implementation demonstrates how passkey security can be combined with JWT efficiency to create a practical authentication solution.

- Remove Ethereum dependencies.
- Use SQLite for credential storage.
- Add comprehensive documentation, including guides for signing and verification flows.
- Add integrate tests.
- Update project structure and configuration files accordingly.
…key attestation.

- Update signing flow to register JWT keys once and sign multiple JWTs without passkey interaction.
- Implement new database structure for storing JWT keys and passkey attestations.
- Replace hybrid verification with detached verification process.
- Enhance documentation and guides for the new flow.
- Add tests for the detached signature architecture.
- Rename database tables and update queries to reflect new passkey architecture.
- Introduce separate temp test database file for improved testing environment.
- Update related functions and tests to align with new naming conventions and structures.
@Muhammad-Altabba Muhammad-Altabba marked this pull request as ready for review November 7, 2025 17:57
@Muhammad-Altabba Muhammad-Altabba changed the title [DRAFT] Transform PoC to PassKey (WebAuthn) & JWT signing Transform PoC to PassKey (WebAuthn) & JWT signing Nov 7, 2025
@cygnusv cygnusv merged commit ff921e6 into main Nov 21, 2025
@cygnusv cygnusv mentioned this pull request Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants