# How Buzz Handles NIP-42 Authentication Over WebSockets

> Discover how Buzz handles NIP-42 authentication over WebSockets using a challenge-response handshake with signed AUTH events. Learn about the client and relay's roles.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: how-to-guide
- Published: 2026-08-28

---

**Buzz implements NIP-42 authentication as a challenge-response handshake over WebSockets, where the relay issues a random 32-byte challenge and the client responds with a signed kind 22242 AUTH event containing that challenge and a host-bound relay tag.**

The [Buzz](https://github.com/block/buzz) Nostr relay uses a layered authentication architecture to enforce NIP-42 requirements before allowing restricted operations. This WebSocket-based flow ensures that clients cryptographically prove ownership of their public key while binding the authentication to a specific relay host.

## NIP-42 Challenge-Response Flow Overview

When a WebSocket connection opens, Buzz immediately initiates the NIP-42 handshake. The flow consists of three distinct phases: challenge issuance, client response, and server verification.

The relay generates a random 32-byte hex challenge in [`crates/buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/nip42.rs) using `generate_challenge()`, storing it in the connection state. The client must then construct a kind 22242 AUTH event containing this challenge in a `["challenge", "..."]` tag and a `["relay", "wss://..."]` tag matching the host URL. Finally, the relay verifies the signature and challenge match before upgrading the connection's `AuthState` to authenticated.

## Client-Side Authentication Implementation

### Initiating the WebSocket Connection

The `WsConnection` struct in [`crates/buzz-ws-client/src/connection.rs`](https://github.com/block/buzz/blob/main/crates/buzz-ws-client/src/connection.rs) manages the TCP/WebSocket lifecycle. Upon connection establishment, the client automatically receives the AUTH challenge from the relay.

```rust
use buzz_ws_client::WsConnection;

// Open the socket and receive the NIP-42 challenge
let mut conn = WsConnection::new("wss://relay.example.com").await?;
let challenge = conn.receive_auth_challenge().await?;

```

The `receive_auth_challenge()` method parses the relay's initial challenge message and returns the hex-encoded random string required for the next step.

### Generating and Signing the AUTH Event

The client constructs a kind 22242 event using the `EventBuilder` from the `buzz-keys` crate. This event must include the challenge string and the relay host for binding verification.

```rust
use buzz_keys::EventBuilder;

// Build the NIP-42 AUTH event (kind 22242)
let auth_event = EventBuilder::new(22242)
    .content("")
    .add_tag(["relay", conn.url().host_str().unwrap()])
    .add_tag(["challenge", challenge])
    .sign(&keys)?;

```

The `buzz-ws-client::publish_event` helper simplifies this by wrapping the signing and transmission logic. After signing, the client sends the event via `conn.send_message(buzz_ws_client::Message::Auth(auth_event)).await?`.

## Server-Side Verification and State Management

### Challenge Verification in the Relay

The `handle_auth` function in [`crates/buzz-relay/src/handlers/auth.rs`](https://github.com/block/buzz/blob/main/crates/buzz-relay/src/handlers/auth.rs) processes incoming AUTH events. It extracts the challenge and relay tags, then calls `verify_nip42_event()` from [`crates/buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/nip42.rs) to perform cryptographic validation.

```rust
use buzz_auth::nip42::{verify_nip42_event, AuthContext};

fn handle_auth(event: Event, expected_relay: &str) -> Result<AuthContext, AuthError> {
    // Verifies signature, challenge match, and host binding
    verify_nip42_event(event, expected_relay)
}

```

Verification ensures the event is correctly signed, contains the expected challenge previously sent to that connection, and that the `relay` tag matches the host URL (preventing replay attacks across different relays).

### AuthState Tracking and Timeouts

The connection maintains an `AuthState` enum defined in [`crates/buzz-relay/src/connection.rs`](https://github.com/block/buzz/blob/main/crates/buzz-relay/src/connection.rs) that transitions from `Unauthenticated` → `WaitingForChallenge` → `Authenticated`. If the client fails to complete the handshake within the configurable timeout (default 5 seconds), the connection closes automatically.

After successful verification, handlers across the relay—such as `relay_admin`, `moderation_commands`, and `ingest`—gate their logic on `conn.authenticated_pubkey()`, applying additional auth policies like API-token checks or ban lists based on the verified public key.

## Practical Integration Examples

### Automatic Authentication with WsClient

For most applications, the high-level `WsClient` API handles the entire NIP-42 handshake transparently.

```rust
use buzz_ws_client::WsClient;
use buzz_keys::Keypair;

// Load private key and connect with automatic NIP-42 auth
let keys = Keypair::from_secret_key_env().unwrap();
let client = WsClient::connect_and_authenticate("wss://relay.example.com", &keys)
    .await
    .expect("NIP-42 handshake failed");

// Client is ready for normal Nostr operations
client.publish_event(event).await?;

```

### Manual Handshake for Custom Agents

Custom clients or debugging tools can manually perform each step of the handshake for finer control over the authentication process.

```rust
// 1. Open socket
let mut conn = buzz_ws_client::WsConnection::new("wss://relay.example.com").await?;

// 2. Receive challenge
let challenge = conn.receive_auth_challenge().await?;

// 3. Build AUTH event
let auth_event = buzz_keys::EventBuilder::new(22242)
    .content("")
    .add_tag(["relay", "relay.example.com"])
    .add_tag(["challenge", challenge])
    .sign(&keys)?;

// 4. Send AUTH response
conn.send_message(buzz_ws_client::Message::Auth(auth_event)).await?;

// 5. Proceed with REQ/EVENT/COUNT

```

## Summary

- **Challenge Generation**: Buzz generates a random 32-byte hex challenge in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs) upon WebSocket connection.
- **Client Response**: Clients create kind 22242 events containing the challenge and host-bound relay tag using `buzz-ws-client` helpers.
- **Server Verification**: The relay verifies signatures, challenge matches, and host binding in [`buzz-relay/src/handlers/auth.rs`](https://github.com/block/buzz/blob/main/buzz-relay/src/handlers/auth.rs).
- **State Management**: `AuthState` tracks authentication progress with a default 5-second timeout for handshake completion.
- **Policy Enforcement**: Post-authentication, the relay applies permission checks based on the verified public key extracted from the NIP-42 event.

## Frequently Asked Questions

### What is the purpose of the relay tag in NIP-42 AUTH events?

The relay tag binds the authentication event to a specific host URL, preventing replay attacks where an attacker intercepts a valid AUTH event and reuse it on a different relay. `verify_nip42_event` in [`crates/buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/nip42.rs) explicitly checks that the tag value matches the expected relay host.

### How does Buzz handle authentication timeouts?

Buzz enforces a configurable timeout (defaulting to 5 seconds) on the `AuthState` transition from `WaitingForChallenge` to `Authenticated`. If the client fails to send a valid kind 22242 event within this window, [`crates/buzz-relay/src/connection.rs`](https://github.com/block/buzz/blob/main/crates/buzz-relay/src/connection.rs) closes the WebSocket connection automatically.

### Can I disable NIP-42 authentication on a Buzz relay?

While the code supports optional authentication policies for different handlers, the NIP-42 challenge is always issued upon connection in the current implementation. Administrative handlers and moderation commands specifically require `authenticated_pubkey()` to return a valid key, making authentication mandatory for privileged operations.

### What happens if the AUTH event signature is invalid?

The `handle_auth` function in [`crates/buzz-relay/src/handlers/auth.rs`](https://github.com/block/buzz/blob/main/crates/buzz-relay/src/handlers/auth.rs) rejects events that fail `verify_nip42_event`, returning an error that prevents the connection state from transitioning to `Authenticated`. The connection remains in an unauthenticated state and may be disconnected if restricted operations are attempted.