# agent-did-server

[![npm version](https://badge.fury.io/js/agent-did-server.svg)](https://www.npmjs.com/package/agent-did-server)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/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](https://www.npmjs.com/package/agent-did) CLI.

```bash
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

```typescript
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

```typescript
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:**
```json
{
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
}
```

**Response:**
```json
{
  "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:**
```json
{
  "challengeId": "550e8400-e29b-41d4-a716-446655440000",
  "did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "payloadB64": "eyJub25jZSI6IlE1Vk5COGpCbnA2OGV2XzlaOG5TNHlMWld4UjdidkZTbVcycktKVzE3VG8iLCJhdWQiOiJteS1hcHAiLCJkb21haW4iOiJleGFtcGxlLmNvbSIsImlhdCI6MTcwNTMxODIwMCwiZXhwIjoxNzA1MzE4MzIwLCJkaWQiOiJkaWQ6a2V5Ono2TWtoYVhnQlpEdm90RGtMNTI1N2ZhaXp0aUdpQzJRdEtMR3Bibm5FR3RhMmRvSyJ9",
  "signature": "G_8u2IKtJcW7eLWgRXQWpHCwvDF5mzODp2kT9HoLLHU8cHjiNYddfQN3JDFruS2fOheYqcJ8mfeCKH298V8oAA",
  "alg": "EdDSA"
}
```

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

### `GET /account` - Protected Endpoint Example

Example protected endpoint that requires JWT authentication.

**Request:**
```http
GET /account
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
```

**Response:**
```json
{
  "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

```bash
# 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.

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

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

### SQLite

Best for single-server production deployments. Persistent storage.

```typescript
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.

```typescript
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.

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

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

---

## Configuration Options

```typescript
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:

```typescript
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):
   ```bash
   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:

```typescript
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:

```bash
# 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:

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

---

## Testing

```bash
npm test
npm run test:watch
npm run test:coverage
```

---

## Examples

See the [examples/](./examples/) directory for:
- Basic standalone server
- SQLite integration
- PostgreSQL integration
- Redis integration
- Custom protected routes
- Next.js integration

---

## Documentation

- **[STANDARDS.md](./docs/STANDARDS.md)** - W3C DID authentication protocol specification
- **[INTEGRATION.md](./docs/INTEGRATION.md)** - Integration guide for existing apps
- **[ADAPTERS.md](./docs/ADAPTERS.md)** - Creating custom database adapters

---

## Related Projects

- **[agent-did](https://github.com/dantber/agent-did)** - CLI for creating DIDs and signing challenges
- **[agent-did.xyz](https://agent-did.xyz)** - Project website and documentation

---

## Standards Compliance

- **W3C DID Core 1.0**: [https://www.w3.org/TR/did-core/](https://www.w3.org/TR/did-core/)
- **RFC 8032**: Edwards-Curve Digital Signature Algorithm (EdDSA)
- **RFC 7519**: JSON Web Token (JWT)

---

## License

MIT - see [LICENSE](LICENSE)

---

## Contributing

Contributions are welcome! Please open an issue or PR at [github.com/dantber/agent-did-server](https://github.com/dantber/agent-did-server)

---

**Built with ❤️ for the agentic future**
