Files
c-relay/docs/NIP-42_Authentication.md
2025-09-13 08:49:09 -04:00

8.5 KiB

NIP-42 Authentication Implementation

Overview

This relay implements NIP-42 (Authentication of clients to relays) providing granular authentication controls for event submission and subscription operations. The implementation supports both challenge-response authentication and per-connection state management.

Architecture

Core Components

  1. Per-Session Authentication State (struct per_session_data)

    • authenticated: Boolean flag indicating authentication status
    • authenticated_pubkey[65]: Hex-encoded public key of authenticated user
    • active_challenge[65]: Current authentication challenge
    • challenge_created: Timestamp when challenge was generated
    • challenge_expires: Challenge expiration timestamp
    • nip42_auth_required_events: Whether auth is required for EVENT submission
    • nip42_auth_required_subscriptions: Whether auth is required for REQ operations
    • auth_challenge_sent: Flag indicating if challenge has been sent
  2. Challenge Management (via request_validator.c)

    • nostr_nip42_generate_challenge(): Generates cryptographically secure challenges
    • nostr_nip42_verify_auth_event(): Validates signed authentication events
    • Challenge storage and cleanup with expiration handling
  3. WebSocket Protocol Integration

    • AUTH message handling in nostr_relay_callback()
    • Challenge generation and transmission
    • Authentication verification and session state updates

Configuration Options

Event-Based Configuration

NIP-42 authentication is configured using kind 33334 configuration events with the following tags:

Tag Description Default Values
nip42_auth_required_events Require auth for EVENT submission false true/false
nip42_auth_required_subscriptions Require auth for REQ operations false true/false

Example Configuration Event

{
  "kind": 33334,
  "content": "C Nostr Relay Configuration",
  "tags": [
    ["d", "<relay_pubkey>"],
    ["nip42_auth_required_events", "true"],
    ["nip42_auth_required_subscriptions", "false"],
    ["relay_description", "Authenticated Nostr Relay"]
  ],
  "created_at": 1640995200,
  "pubkey": "<admin_pubkey>",
  "id": "<event_id>",
  "sig": "<signature>"
}

Authentication Flow

1. Challenge Generation

When authentication is required and client is not authenticated:

Client -> Relay: ["EVENT", <event>] (unauthenticated)
Relay -> Client: ["AUTH", <challenge>]

The challenge is a 64-character hex string generated using cryptographically secure random numbers.

2. Authentication Response

Client creates and signs an authentication event (kind 22242):

{
  "kind": 22242,
  "content": "",
  "tags": [
    ["relay", "ws://relay.example.com"],
    ["challenge", "<challenge_from_relay>"]
  ],
  "created_at": <current_timestamp>,
  "pubkey": "<client_pubkey>",
  "id": "<event_id>",
  "sig": "<signature>"
}

Client sends this event back to relay:

Client -> Relay: ["AUTH", <signed_auth_event>]

3. Verification and Session Update

The relay:

  1. Validates the authentication event signature
  2. Verifies the challenge matches the one sent
  3. Checks challenge expiration (default: 10 minutes)
  4. Updates session state with authenticated public key
  5. Sends confirmation notice
Relay -> Client: ["NOTICE", "NIP-42 authentication successful"]

Granular Authentication Controls

Separate Controls for Events vs Subscriptions

The implementation provides separate authentication requirements:

  • Event Submission: Control whether clients must authenticate to publish events
  • Subscription Access: Control whether clients must authenticate to create subscriptions

This allows flexible relay policies:

  • Public Read, Authenticated Write: events=true, subscriptions=false
  • Fully Authenticated: events=true, subscriptions=true
  • Public Access: events=false, subscriptions=false (default)
  • Authenticated Read Only: events=false, subscriptions=true

Per-Connection State

Each WebSocket connection maintains its own authentication state:

  • Authentication persists for the lifetime of the connection
  • Challenges expire after 10 minutes
  • Session cleanup on connection close

Security Features

Challenge Security

  • 64-character hexadecimal challenges (256 bits of entropy)
  • Cryptographically secure random generation
  • Challenge expiration to prevent replay attacks
  • One-time use challenges

Event Validation

  • Complete signature verification using secp256k1
  • Event ID validation
  • Challenge-response binding verification
  • Timestamp validation with configurable tolerance

Session Management

  • Thread-safe per-session state management
  • Automatic cleanup on disconnection
  • Challenge expiration handling

Client Integration

Using nak Client

# Generate keypair
PRIVKEY=$(nak key --gen)
PUBKEY=$(nak key --pub $PRIVKEY)

# Connect and authenticate automatically
nak event -k 1 --content "Authenticated message" --sec $PRIVKEY --relay ws://localhost:8888

# nak handles NIP-42 authentication automatically when required

Manual WebSocket Integration

const ws = new WebSocket('ws://localhost:8888');

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  
  if (message[0] === 'AUTH') {
    const challenge = message[1];
    
    // Create auth event (kind 22242)
    const authEvent = {
      kind: 22242,
      content: "",
      tags: [
        ["relay", "ws://localhost:8888"],
        ["challenge", challenge]
      ],
      created_at: Math.floor(Date.now() / 1000),
      pubkey: clientPubkey,
      // ... calculate id and signature
    };
    
    // Send auth response
    ws.send(JSON.stringify(["AUTH", authEvent]));
  }
};

// Send event (may trigger AUTH challenge)
ws.send(JSON.stringify(["EVENT", myEvent]));

Administration

Enabling Authentication

  1. Get Admin Private Key: Extract from relay startup logs (shown once)
  2. Create Configuration Event: Use nak or custom tooling
  3. Publish Configuration: Send to relay with admin signature
# Enable auth for events only
nak event -k 33334 \
  --content "C Nostr Relay Configuration" \
  --tag "d=$RELAY_PUBKEY" \
  --tag "nip42_auth_required_events=true" \
  --tag "nip42_auth_required_subscriptions=false" \
  --sec $ADMIN_PRIVKEY \
  --relay ws://localhost:8888

Monitoring Authentication

  • Check relay logs for authentication events
  • Monitor NOTICE messages for auth status
  • Use get_settings.sh script to view current configuration
./get_settings.sh

Troubleshooting

Common Issues

  1. Challenge Expiration

    • Default: 10 minutes
    • Client must respond within expiration window
    • Generate new challenge for expired attempts
  2. Signature Verification Failures

    • Verify event structure matches NIP-42 specification
    • Check challenge value matches exactly
    • Ensure proper secp256k1 signature generation
  3. Configuration Not Applied

    • Verify admin private key is correct
    • Check configuration event signature
    • Ensure relay pubkey in 'd' tag matches relay

Debug Commands

# Check supported NIPs
curl -H "Accept: application/nostr+json" http://localhost:8888 | jq .supported_nips

# View current configuration
nak req -k 33334 ws://localhost:8888 | jq .

# Test authentication flow
./tests/42_nip_test.sh

Performance Considerations

  • Challenge generation: ~1ms overhead per unauthenticated connection
  • Authentication verification: ~2-5ms per auth event
  • Memory overhead: ~200 bytes per connection for auth state
  • Database impact: Configuration events cached, minimal query overhead

Integration with Other NIPs

NIP-01 (Basic Protocol)

  • AUTH messages integrated into standard WebSocket flow
  • Compatible with existing EVENT/REQ/CLOSE message handling

NIP-11 (Relay Information)

  • NIP-42 advertised in supported_nips array
  • Authentication requirements reflected in relay metadata

NIP-20 (Command Results)

  • OK responses include authentication-related error messages
  • NOTICE messages provide authentication status updates

Future Extensions

Potential Enhancements

  • Role-based authentication (admin, user, read-only)
  • Time-based access controls
  • Rate limiting based on authentication status
  • Integration with external authentication providers

Configuration Extensions

  • Per-kind authentication requirements
  • Whitelist/blacklist integration
  • Custom challenge expiration times
  • Authentication logging and metrics