https://github.com/bitkarrot/relay-auth-test
Explore 4 demos for NIP-42 Relay Auth
https://github.com/bitkarrot/relay-auth-test
Last synced: 8 months ago
JSON representation
Explore 4 demos for NIP-42 Relay Auth
- Host: GitHub
- URL: https://github.com/bitkarrot/relay-auth-test
- Owner: bitkarrot
- Created: 2025-08-08T06:22:26.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-09-23T22:53:32.000Z (9 months ago)
- Last Synced: 2025-09-24T00:22:47.064Z (9 months ago)
- Language: Astro
- Homepage: https://relay-auth-test.vercel.app
- Size: 405 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Nostr NIP-42 AUTH with Kind 30078 Events - Demo Code
4 Demos in this repo - This implementation provides a modular, reusable system for NIP-42 authentication and publishing kind 30078 (Parameterized Replaceable Events) in Astro applications using nostr-tools.
## 🔗 NIP-42 Authentication Flow
```mermaid
sequenceDiagram
participant Client as Client App
(Astro Page)
participant Extension as NIP-07
Extension
participant Relay as Nostr Relay
(ws://localhost:3334)
Note over Client, Relay: Phase 1: Initial Connection & Challenge
Client->>Relay: 1. Connect WebSocket
activate Relay
Client->>Relay: 2. Send REQ (trigger AUTH)
Note right of Client: ["REQ", "auth-trigger", {"limit": 1}]
Relay->>Client: 3. AUTH Challenge
Note left of Relay: ["AUTH", ""]
Note over Client, Relay: Phase 2: Authentication Response
Client->>Extension: 4. Get Public Key
activate Extension
Extension-->>Client: pubkey
deactivate Extension
Client->>Client: 5. Create AUTH Event
Note right of Client: kind: 22242
tags: [["relay", "ws://..."], ["challenge", "..."]]
content: ""
Client->>Extension: 6. Sign AUTH Event
activate Extension
Extension-->>Client: Signed AUTH Event
deactivate Extension
Client->>Relay: 7. Send Signed AUTH
Note right of Client: ["AUTH", signed-auth-event]
Relay->>Client: 8. AUTH Response (OK/FAIL)
Note left of Relay: ["OK", event-id, true/false, message]
alt Authentication Successful
Relay-->>Client: ✅ Connection Authenticated
Note over Client, Relay: Same connection remains open
Note over Client, Relay: Phase 3: Publish Kind 30078 Event
Client->>Client: 9. Create Kind 30078 Event
Note right of Client: kind: 30078
tags: [["d", "unique-tag"], ...]
content: "event content"
Client->>Extension: 10. Sign Kind 30078 Event
activate Extension
Extension-->>Client: Signed Event
deactivate Extension
Client->>Relay: 11. Publish Event
Note right of Client: ["EVENT", signed-30078-event]
Relay->>Client: 12. Publish Response (OK/FAIL)
Note left of Relay: ["OK", event-id, true/false, message]
alt Publish Successful
Relay-->>Client: ✅ Event Published
Note right of Client: Event ID returned
else Publish Failed
Relay-->>Client: ❌ Publish Error
Note right of Client: Error message provided
end
else Authentication Failed
Relay-->>Client: ❌ AUTH Failed
Note right of Client: Connection not authenticated
Cannot publish events
end
Note over Client, Relay: Phase 4: Connection Management
opt Later Operations
Client->>Relay: Additional Events
Note right of Client: Can publish more events
using same authenticated connection
end
opt Disconnect
Client->>Relay: Close Connection
deactivate Relay
Note over Client, Relay: Connection closed
Must re-authenticate for new session
end
```
# NIP-42 Authentication Message Details
## 🔄 **Message Flow Breakdown**
### **Phase 1: Connection & Challenge**
#### 1. WebSocket Connection
```javascript
// Client connects to relay
const ws = new WebSocket('ws://localhost:3334')
```
#### 2. Trigger AUTH Challenge
```json
["REQ", "auth-trigger", {"limit": 1}]
```
- **Purpose**: Many relays only send AUTH challenges when a restricted operation is attempted
- **Effect**: Prompts relay to send AUTH challenge if required
#### 3. AUTH Challenge from Relay
```json
["AUTH", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"]
```
- **Format**: `["AUTH", ""]`
- **Challenge**: Random string that must be included in AUTH response
- **Security**: Prevents replay attacks
### **Phase 2: Authentication Response**
#### 4. NIP-07 Public Key Request
```javascript
const pubkey = await window.nostr.getPublicKey()
```
- **Returns**: User's public key (hex format)
- **Permission**: May trigger extension permission prompt
#### 5. Create AUTH Event (Kind 22242)
```json
{
"kind": 22242,
"created_at": 1699123456,
"tags": [
["relay", "ws://localhost:3334"],
["challenge", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"]
],
"content": "",
"pubkey": "user-pubkey-hex"
}
```
- **Kind 22242**: Special event type for NIP-42 authentication
- **Required Tags**:
- `relay`: The relay URL being authenticated to
- `challenge`: The exact challenge string received
- **Content**: Always empty for AUTH events
#### 6. Sign AUTH Event
```javascript
const signedAuthEvent = await window.nostr.signEvent(authEvent)
```
- **Adds**: `id` (event hash) and `sig` (signature) fields
- **Security**: Cryptographically proves ownership of private key
#### 7. Send AUTH Response
```json
["AUTH", {
"kind": 22242,
"created_at": 1699123456,
"tags": [["relay", "ws://localhost:3334"], ["challenge", "..."]],
"content": "",
"pubkey": "...",
"id": "event-hash",
"sig": "signature"
}]
```
#### 8. Relay AUTH Verification
```json
["OK", "event-id", true, ""]
```
- **Format**: `["OK", event-id, success, message]`
- **Success**: `true` = authenticated, `false` = failed
- **Message**: Error description if failed
### **Phase 3: Event Publishing**
#### 9. Create Kind 30078 Event
```json
{
"kind": 30078,
"created_at": 1699123456,
"tags": [
["d", "unique-identifier"],
["t", "example"],
["client", "astro-app"]
],
"content": "Hello, authenticated Nostr!",
"pubkey": "user-pubkey-hex"
}
```
- **Kind 30078**: Parameterized Replaceable Event
- **Required Tag**: `["d", "identifier"]` - makes event replaceable
- **Additional Tags**: Custom tags for categorization, client info, etc.
#### 10. Sign Kind 30078 Event
```javascript
const signedEvent = await window.nostr.signEvent(event)
```
#### 11. Publish Event
```json
["EVENT", {
"kind": 30078,
"created_at": 1699123456,
"tags": [["d", "unique-id"], ["t", "example"]],
"content": "Hello, authenticated Nostr!",
"pubkey": "...",
"id": "event-hash",
"sig": "signature"
}]
```
#### 12. Publish Confirmation
```json
["OK", "event-id", true, ""]
```
## 🔐 **Security Features**
### **Challenge-Response Authentication**
- **Unique Challenge**: Each AUTH attempt gets a unique challenge
- **Replay Protection**: Old AUTH events cannot be reused
- **Time Sensitivity**: AUTH events typically have short validity windows
### **Cryptographic Verification**
- **Digital Signatures**: All events are cryptographically signed
- **Public Key Verification**: Relay verifies signature matches claimed pubkey
- **Event Integrity**: Event ID is hash of event content, preventing tampering
### **Connection Security**
- **Persistent Auth**: Authentication persists for the WebSocket connection lifetime
- **Per-Connection**: Each new connection requires fresh authentication
- **Selective Access**: Relays can require AUTH for specific operations only
## ⚠️ **Critical Implementation Notes**
### **Connection Persistence**
```javascript
// ✅ CORRECT: Use same connection for AUTH and publishing
const relay = await pool.ensureRelay(url)
await authenticate(relay) // AUTH on this connection
await publishEvent(relay) // Publish on SAME connection
// ❌ WRONG: New connection loses authentication
const relay1 = await pool.ensureRelay(url)
await authenticate(relay1)
relay1.close()
const relay2 = await pool.ensureRelay(url) // New connection!
await publishEvent(relay2) // Will fail - not authenticated
```
### **Challenge Handling**
```javascript
// ✅ CORRECT: Use exact challenge from relay
relay.on('auth', (challenge) => {
const authEvent = {
tags: [['challenge', challenge]] // Exact challenge
}
})
// ❌ WRONG: Modified or old challenge
const authEvent = {
tags: [['challenge', 'old-challenge']] // Will be rejected
}
```
### **Event Structure**
```javascript
// ✅ CORRECT: Proper kind 30078 with d-tag
const event = {
kind: 30078,
tags: [['d', 'unique-id']] // Required for parameterized replaceable
}
// ❌ WRONG: Missing d-tag
const event = {
kind: 30078,
tags: [['t', 'topic']] // Missing required d-tag
}
```
## 🎯 **Implementation Tips**
1. **Always handle AUTH challenges immediately**
2. **Keep the same WebSocket connection alive**
3. **Implement proper timeout handling**
4. **Validate all event structures before signing**
5. **Handle extension permission requests gracefully**
6. **Provide clear error messages to users**
7. **Test with different relay implementations**
## 🚀 Features
- **NIP-42 Authentication**: Complete relay authentication flow
- **Kind 30078 Events**: Publish parameterized replaceable events after auth
- **NIP-07 Integration**: Works with browser extension signers (Alby, nos2x, etc.)
- **Persistent Connection**: Maintains same connection for AUTH and event publishing
- **Modular Design**: Reusable across multiple Astro pages
- **Environment Configuration**: Configurable relay URLs via environment variables
- **TypeScript Support**: Full type safety and IntelliSense
- **Error Handling**: Comprehensive error handling and user feedback
## 📁 Project Structure
```
src/
├── lib/
│ └── nostr-auth.ts # Core authentication service
├── composables/
│ └── useNostrAuth.ts # Reusable composable with UI helpers
├── pages/
│ ├── nostr-auth-example.astro # Complete example page
│ └── simple-nostr-example.astro # Simple usage example
└── env.d.ts # Environment type definitions
```
## ⚙️ Installation
1. **Install dependencies**:
```bash
npm install nostr-tools
npm install -D @types/node typescript
```
2. **Set up environment variables**:
```bash
# .env
HIVETALK_RELAYS=ws://localhost:3334
```
3. **Configure Astro** (astro.config.mjs):
```js
import { defineConfig } from 'astro/config';
export default defineConfig({
vite: {
define: {
'process.env.HIVETALK_RELAYS': JSON.stringify(process.env.HIVETALK_RELAYS || 'ws://localhost:3334')
}
}
});
```
## 🔧 Core Components
### NostrAuthService (`src/lib/nostr-auth.ts`)
The main authentication service that handles:
- NIP-42 authentication flow
- WebSocket connection management
- Event signing and publishing
- Error handling and timeouts
**Key Methods**:
- `authenticate()`: Perform NIP-42 AUTH with relay
- `publishKind30078Event()`: Publish parameterized replaceable events
- `disconnect()`: Clean up connections
- `getAuthStatus()`: Check authentication state
### UseNostrAuth Composable (`src/composables/useNostrAuth.ts`)
A higher-level composable that provides:
- State management with reactive updates
- UI helper utilities
- Simplified API for common operations
- Event subscription system
**Key Features**:
- `subscribe()`: Listen to auth state changes
- `createUIManager()`: Automatic DOM updates
- Environment variable integration
- Error state management
## 📄 Kind 30078 Events
Kind 30078 events are Parameterized Replaceable Events that:
- Require a "d" tag as unique identifier
- Can be updated/replaced by publishing new events with same d-tag
- Support additional custom tags
- Must be published on authenticated connections (if relay requires AUTH)
**Event Structure**:
```json
{
"kind": 30078,
"created_at": 1699123456,
"tags": [
["d", "unique-identifier"],
["t", "custom-tag"],
["client", "my-app"]
],
"content": "Event content here",
"pubkey": "...",
"id": "...",
"sig": "..."
}
```
## 🛠️ Configuration Options
### NostrAuthService Options
```typescript
interface AuthConfig {
relayUrl: string // WebSocket relay URL
timeout?: number // Operation timeout in ms (default: 10000)
}
```
### UseNostrAuth Options
```typescript
interface UseNostrAuthOptions {
relayUrl?: string // Override relay URL
timeout?: number // Operation timeout
autoConnect?: boolean // Auto-authenticate when extension available
}
```
## 🔒 Security Considerations
1. **Extension Security**: Always verify NIP-07 extension availability
2. **Connection Persistence**: Maintain same connection for AUTH and publishing
3. **Event Validation**: Validate all event data before signing
4. **Error Handling**: Never expose sensitive information in error messages
5. **Timeout Management**: Use appropriate timeouts to prevent hanging
## 🐛 Troubleshooting
### Common Issues
**"NIP-07 extension not found"**
- Install a Nostr extension (Alby, nos2x, etc.)
- Refresh the page after installation
**"Authentication timeout"**
- Check relay URL and availability
- Increase timeout in configuration
- Verify relay supports NIP-42
**"Event publish failed"**
- Ensure authentication completed successfully
- Check d-tag uniqueness for replaceable events
- Verify relay accepts kind 30078 events
**"Connection lost"**
- Don't disconnect between AUTH and publishing
- Handle WebSocket connection errors gracefully
- Implement reconnection logic if needed
### Debugging Tips
1. **Enable Console Logging**: All operations log detailed information
2. **Check Network Tab**: Verify WebSocket messages in browser dev tools
3. **Test Relay**: Use a simple WebSocket client to test relay connectivity
4. **Validate Environment**: Ensure HIVETALK_RELAYS is properly set
## 📚 References
- [NIP-42: Authentication of clients to relays](https://github.com/nostr-protocol/nips/blob/master/42.md)
- [NIP-01: Basic protocol flow](https://github.com/nostr-protocol/nips/blob/master/01.md)
- [NIP-07: Browser extension interface](https://github.com/nostr-protocol/nips/blob/master/07.md)
- [Nostr Tools Documentation](https://github.com/nbd-wtf/nostr-tools)
## 🤝 Contributing
This implementation is designed to be modular and extensible. Feel free to:
- Add support for additional event kinds
- Implement reconnection logic
- Add more sophisticated error handling
- Create additional UI components
## 📄 License
This code is provided as-is for educational and development purposes. Please review and test thoroughly before using in production applications.