npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agent-did-server

v0.1.1

Published

Production-ready W3C DID authentication server for AI agents and applications

Readme

agent-did-server

npm version License: MIT

Production-ready W3C DID authentication server for AI agents and applications.

Implements the complete challenge-response authentication flow using Decentralized Identifiers (DIDs) and Ed25519 signatures. Works seamlessly with the agent-did CLI.

npm install agent-did-server express

Features

Complete Authentication Flow

  • Challenge generation with cryptographic nonces
  • Ed25519 signature verification
  • JWT issuance and validation
  • Protected route middleware

Multiple Database Adapters

  • In-Memory - Development and testing
  • SQLite - Single-server production
  • PostgreSQL - Multi-server production
  • Redis - High-traffic with automatic expiration

Production-Ready

  • TypeScript with full type safety
  • Security headers (Helmet)
  • CORS configuration
  • Automatic challenge cleanup
  • Comprehensive error handling

Flexible Integration

  • Use as standalone server
  • Add to existing Express apps
  • Framework-agnostic middleware
  • Customizable routes and adapters

Quick Start

Standalone Server

import { createServer } from 'agent-did-server';

const app = createServer({
  jwtSecret: process.env.JWT_SECRET!,
  jwtIssuer: 'did:web:example.com',
  audience: 'my-app',
  domain: 'example.com',
});

app.listen(3000, () => {
  console.log('DID Auth Server running on port 3000');
});

Add to Existing Express App

import express from 'express';
import {
  createAuthRoute,
  createVerifyRoute,
  authenticate,
  MemoryAdapter,
} from 'agent-did-server';

const app = express();
app.use(express.json());

const config = {
  jwtSecret: process.env.JWT_SECRET!,
  jwtIssuer: 'did:web:example.com',
  jwtExpiresIn: 900, // 15 minutes
  challengeExpiresIn: 120, // 2 minutes
  audience: 'my-app',
  domain: 'example.com',
  adapter: new MemoryAdapter(),
};

// Add DID authentication routes
app.use(createAuthRoute(config));
app.use(createVerifyRoute(config));

// Protect your routes
app.get(
  '/api/data',
  authenticate(config.jwtSecret, config.jwtIssuer),
  (req, res) => {
    const { did } = req.auth!;
    res.json({ message: `Hello, ${did}!`, data: 'protected' });
  }
);

app.listen(3000);

API Endpoints

POST /auth - Request Challenge

Generate an authentication challenge for a DID.

Request:

{
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
}

Response:

{
  "challengeId": "550e8400-e29b-41d4-a716-446655440000",
  "nonce": "Q5VNB8jBnp68ev_9Z8nS4yLZWxR7bvFSmW2rKJW17To",
  "expiresAt": "2024-02-04T10:32:00Z",
  "audience": "my-app",
  "domain": "example.com"
}

POST /verify - Verify Signature & Get JWT

Verify the signed challenge and receive a JWT for authenticated sessions.

Request:

{
  "challengeId": "550e8400-e29b-41d4-a716-446655440000",
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "payloadB64": "eyJub25jZSI6IlE1Vk5COGpCbnA2OGV2XzlaOG5TNHlMWld4UjdidkZTbVcycktKVzE3VG8iLCJhdWQiOiJteS1hcHAiLCJkb21haW4iOiJleGFtcGxlLmNvbSIsImlhdCI6MTcwNTMxODIwMCwiZXhwIjoxNzA1MzE4MzIwLCJkaWQiOiJkaWQ6a2V5Ono2TWtoYVhnQlpEdm90RGtMNTI1N2ZhaXp0aUdpQzJRdEtMR3Bibm5FR3RhMmRvSyJ9",
  "signature": "G_8u2IKtJcW7eLWgRXQWpHCwvDF5mzODp2kT9HoLLHU8cHjiNYddfQN3JDFruS2fOheYqcJ8mfeCKH298V8oAA",
  "alg": "EdDSA"
}

Response:

{
  "jwt": "eyJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6ImFnZW50OmF1dGgiLCJzdWIiOiJkaWQ6a2V5Ono2TWtoYVhnQlpEdm90RGtMNTI1N2ZhaXp0aUdpQzJRdEtMR3Bibm5FR3RhMmRvSyIsImlzcyI6ImRpZDp3ZWI6ZXhhbXBsZS5jb20iLCJpYXQiOjE3MDUzMTgyMDAsImV4cCI6MTcwNTMxOTEwMH0.signature",
  "expiresAt": "2024-02-04T10:47:00Z"
}

GET /account - Protected Endpoint Example

Example protected endpoint that requires JWT authentication.

Request:

GET /account
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...

Response:

{
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "scope": "agent:auth",
  "issuer": "did:web:example.com",
  "authenticated": true,
  "issuedAt": 1705318200,
  "expiresAt": 1705319100
}

Complete Authentication Flow

Using agent-did CLI

# 1. Request challenge
curl -X POST http://localhost:3000/auth \
  -H "Content-Type: application/json" \
  -d '{"did": "did:key:z6Mkj7yH..."}'

# Response: { "challengeId": "...", "nonce": "...", ... }

# 2. Sign challenge with agent-did CLI
agent-did auth sign \
  --did "did:key:z6Mkj7yH..." \
  --challenge "NONCE_FROM_STEP_1" \
  --audience "my-app" \
  --domain "example.com" \
  --json > signed-auth.json

# 3. Verify and get JWT
curl -X POST http://localhost:3000/verify \
  -H "Content-Type: application/json" \
  -d @signed-auth.json

# Response: { "jwt": "...", "expiresAt": "..." }

# 4. Access protected endpoints
curl http://localhost:3000/account \
  -H "Authorization: Bearer YOUR_JWT_HERE"

Database Adapters

In-Memory (Default)

Best for development and testing. Data is lost on restart.

import { createServer, MemoryAdapter } from 'agent-did-server';

const app = createServer({
  adapter: new MemoryAdapter(),
});

SQLite

Best for single-server production deployments. Persistent storage.

import { createServer, SQLiteAdapter } from 'agent-did-server';

const app = createServer({
  adapter: new SQLiteAdapter('./data/challenges.db'),
});

PostgreSQL

Best for multi-server production deployments. Shared database.

import { createServer, PostgresAdapter } from 'agent-did-server';

const app = createServer({
  adapter: new PostgresAdapter(process.env.DATABASE_URL!),
});

Redis

Best for high-traffic production. Automatic TTL expiration.

import { createServer, RedisAdapter } from 'agent-did-server';

const app = createServer({
  adapter: new RedisAdapter(process.env.REDIS_URL!),
});

Configuration Options

interface ServerConfig {
  // Required
  jwtSecret: string;           // JWT signing secret (use strong random value)
  jwtIssuer: string;           // JWT issuer identifier (e.g., "did:web:example.com")

  // Optional
  port?: number;               // Server port (default: 3000)
  jwtExpiresIn?: number;       // JWT expiration in seconds (default: 900 = 15 min)
  challengeExpiresIn?: number; // Challenge expiration in seconds (default: 120 = 2 min)
  audience?: string;           // Authentication audience (default: "agent-did-auth")
  domain?: string;             // Server domain (default: "localhost")
  adapter?: ChallengeAdapter;  // Database adapter (default: MemoryAdapter)
  cors?: boolean;              // Enable CORS (default: true)
  corsOrigin?: string | string[]; // CORS origins (default: "*")
}

Protecting Your Routes

Use the authenticate middleware to protect custom routes:

import { authenticate, AuthenticatedRequest } from 'agent-did-server';

// Protect a single route
app.get(
  '/protected',
  authenticate(jwtSecret, jwtIssuer),
  (req: AuthenticatedRequest, res) => {
    const { did, scope } = req.auth!;
    res.json({ message: `Hello, ${did}!` });
  }
);

// Protect multiple routes
const auth = authenticate(jwtSecret, jwtIssuer);

app.get('/route1', auth, handler1);
app.post('/route2', auth, handler2);
app.delete('/route3', auth, handler3);

Security Best Practices

  1. Always use HTTPS in production
  2. Generate strong JWT secrets (32+ bytes, cryptographically random):
    openssl rand -base64 32
  3. Set appropriate CORS origins (don't use * in production)
  4. Use persistent storage (SQLite, PostgreSQL, Redis) in production
  5. Monitor challenge usage to detect replay attacks
  6. Rotate JWT secrets periodically
  7. Set short JWT expiration times (15 minutes recommended)
  8. Rate limit challenge and verify endpoints

Critical Implementation Notes

Ed25519 Signature Verification

This package correctly configures @noble/ed25519 with SHA-512 hashing. Without this configuration, signature verification fails:

import * as ed25519 from '@noble/ed25519';
import { createHash } from 'crypto';

// REQUIRED for @noble/ed25519 v2.x
ed25519.etc.sha512Sync = (...m) =>
  createHash('sha512').update(Buffer.concat(m)).digest();

This is already handled internally by agent-did-server.

Dual Signature Verification

The server tries two verification approaches for maximum compatibility:

  1. Verify signature against base64url-decoded payload bytes
  2. Verify signature against UTF-8-encoded base64url string (agent-did CLI uses this)

Environment Variables

Create a .env file in your project root:

# Server
PORT=3000

# JWT (REQUIRED)
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_ISSUER=did:web:example.com

# Authentication
AUDIENCE=my-app
DOMAIN=example.com

# CORS
CORS_ORIGIN=*

# Database (optional, based on adapter)
# SQLITE_DB_PATH=./data/challenges.db
# DATABASE_URL=postgresql://user:password@localhost:5432/agent_did
# REDIS_URL=redis://localhost:6379

TypeScript Support

Fully typed with TypeScript. Import types as needed:

import type {
  ServerConfig,
  Challenge,
  ChallengeAdapter,
  JWTPayload,
  AuthenticatedRequest,
} from 'agent-did-server';

Testing

npm test
npm run test:watch
npm run test:coverage

Examples

See the examples/ directory for:

  • Basic standalone server
  • SQLite integration
  • PostgreSQL integration
  • Redis integration
  • Custom protected routes
  • Next.js integration

Documentation


Related Projects


Standards Compliance


License

MIT - see LICENSE


Contributing

Contributions are welcome! Please open an issue or PR at github.com/dantber/agent-did-server


Built with ❤️ for the agentic future