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
-
Per-Session Authentication State (
struct per_session_data)authenticated: Boolean flag indicating authentication statusauthenticated_pubkey[65]: Hex-encoded public key of authenticated useractive_challenge[65]: Current authentication challengechallenge_created: Timestamp when challenge was generatedchallenge_expires: Challenge expiration timestampnip42_auth_required_events: Whether auth is required for EVENT submissionnip42_auth_required_subscriptions: Whether auth is required for REQ operationsauth_challenge_sent: Flag indicating if challenge has been sent
-
Challenge Management (via
request_validator.c)nostr_nip42_generate_challenge(): Generates cryptographically secure challengesnostr_nip42_verify_auth_event(): Validates signed authentication events- Challenge storage and cleanup with expiration handling
-
WebSocket Protocol Integration
- AUTH message handling in
nostr_relay_callback() - Challenge generation and transmission
- Authentication verification and session state updates
- AUTH message handling in
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:
- Validates the authentication event signature
- Verifies the challenge matches the one sent
- Checks challenge expiration (default: 10 minutes)
- Updates session state with authenticated public key
- 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
- Get Admin Private Key: Extract from relay startup logs (shown once)
- Create Configuration Event: Use nak or custom tooling
- 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
NOTICEmessages for auth status - Use
get_settings.shscript to view current configuration
./get_settings.sh
Troubleshooting
Common Issues
-
Challenge Expiration
- Default: 10 minutes
- Client must respond within expiration window
- Generate new challenge for expired attempts
-
Signature Verification Failures
- Verify event structure matches NIP-42 specification
- Check challenge value matches exactly
- Ensure proper secp256k1 signature generation
-
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_nipsarray - 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