# How NIP-42 WebSocket Authentication Works in Block Buzz: A Complete Technical Guide

> Learn how NIP-42 WebSocket authentication works in Block Buzz. Discover the challenge-response flow, Schnorr signatures, and host-binding for secure relay access.

- Repository: [Block Open Source/buzz](https://github.com/block/buzz)
- Tags: deep-dive
- Published: 2026-08-29

---

**Block Buzz implements NIP-42 authentication through a challenge-response flow where the relay issues a random challenge, the client signs a kind 22242 event containing that challenge and relay URL, and the server verifies Schnorr signatures and host-binding before granting access to protected operations.**

Block Buzz is a Nostr relay implementation that uses **NIP-42 WebSocket authentication** to secure client connections before allowing event publication or subscription to protected feeds. Unlike simple token-based systems, the protocol uses cryptographic challenge-response verification tied to Nostr keypairs, ensuring only key holders can authenticate. This deep dive examines the complete authentication flow from the initial WebSocket handshake through server-side verification, referencing the actual source code across the `buzz-ws-client`, `buzz-auth`, and `buzz-relay` crates.

## The NIP-42 Challenge-Response Flow

NIP-42 defines a standard method for proving ownership of a Nostr public key during a WebSocket session. Block Buzz splits this implementation between client-side event construction and server-side cryptographic verification.

### Step 1: Initiating the WebSocket Connection

When a client connects to Block Buzz, the authentication process begins immediately upon socket establishment. In [`buzz-ws-client/src/connection.rs`](https://github.com/block/buzz/blob/main/buzz-ws-client/src/connection.rs), the `NostrWsConnection::connect` method establishes the WebSocket and begins listening for the initial `AUTH` challenge from the relay.

The client enters a waiting state after connection, anticipating the relay's challenge message before proceeding with any other operations.

### Step 2: Receiving the AUTH Challenge

The relay transmits a JSON message with the format `["AUTH", {"challenge":"..."}]` immediately after the WebSocket opens. The client parses incoming text frames through `parse_relay_message` and stores the challenge in the `pending_challenge` field of the `NostrWsConnection` struct (lines 41-45 of [`connection.rs`](https://github.com/block/buzz/blob/main/connection.rs)).

This challenge is a random string that prevents replay attacks and binds the authentication to the specific session.

### Step 3: Building the AUTH Event (kind 22242)

Using the stored challenge, the client constructs a **NIP-42 AUTH event** (kind 22242) via the `build_auth_event` function in [`buzz-ws-client/src/message.rs`](https://github.com/block/buzz/blob/main/buzz-ws-client/src/message.rs). This specialized event contains three critical components:

- **`challenge`**: The random string supplied by the relay
- **`relay`**: The URL of the relay for host-binding verification
- **`auth` tag (optional)**: A NIP-OA OAuth-style token for additional authorization layers

The relay URL inclusion prevents cross-relay replay attacks by binding the signature to a specific host.

### Step 4: Signing and Transmitting the Response

The client signs the constructed event with its private key (`keys`) and transmits it back to the relay using `NostrWsConnection::authenticate` (lines 79-84 of [`connection.rs`](https://github.com/block/buzz/blob/main/connection.rs)). The message format follows Nostr's standard structure: `["AUTH", <event>]`.

This transmission contains the cryptographic proof that the client possesses the private key corresponding to their claimed public key.

### Step 5: Server-Side Verification

The Block Buzz relay forwards the AUTH event to the verification service in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs). The `verify_nip42_auth_event` function performs four critical validation checks:

1. **Signature verification**: Schnorr signature validation against the event's public key
2. **Challenge freshness**: The challenge must match the one previously issued to that connection and be ≤ 1024 bytes
3. **Host-binding**: The `relay` tag must contain the same host as the connection URL (per NIP-42 row 44)
4. **Optional NIP-OA validation**: If an `auth` tag is present, it validates as a bearer token

Failure of any check results in immediate rejection with a descriptive error message.

### Step 6: Confirmation and State Management

Upon successful verification, the server returns an **OK** message: `["OK", <event_id>, true, "..."]`. The client waits for this confirmation using `wait_for_ok` and marks authentication as successful upon receiving `accepted == true`, logging `debug!("NIP-42 authentication successful")`.

The relay records the authenticated public key in the connection state ([`buzz-relay/src/handlers/auth.rs`](https://github.com/block/buzz/blob/main/buzz-relay/src/handlers/auth.rs)), attaching the `AuthContext` to the WebSocket session for subsequent access control checks.

## Client-Side Implementation in buzz-ws-client

The client library handles authentication transparently during connection setup. Here's how you would initiate an authenticated connection:

```rust
use buzz_ws_client::{NostrWsConnection, Keys};

// Initialize with your private key
let keys = Keys::generate();

// Connect and automatically handle NIP-42 flow
let mut conn = NostrWsConnection::connect("wss://relay.example.com", keys).await?;

// The connection is now authenticated and ready for operations
conn.send_event(event).await?;

```

The `NostrWsConnection` struct maintains the `pending_challenge` state internally, handling the challenge-response cycle without requiring manual intervention from the application developer.

## Server-Side Verification in buzz-auth

The `buzz-auth` crate provides the cryptographic verification layer used by the relay. The core verification logic in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs) ensures strict compliance with the NIP-42 specification:

```rust
// Pseudo-code representation of the verification flow
pub fn verify_nip42_auth_event(
    event: &Event,
    expected_challenge: &str,
    relay_url: &str
) -> Result<AuthContext, AuthError> {
    // 1. Verify Schnorr signature
    event.verify_signature()?;
    
    // 2. Check challenge matches and size ≤ 1024 bytes
    if event.challenge != expected_challenge || event.challenge.len() > 1024 {
        return Err(AuthError::InvalidChallenge);
    }
    
    // 3. Verify host-binding (relay tag matches connection URL)
    if !event.tags.relay.contains(relay_url) {
        return Err(AuthError::HostMismatch);
    }
    
    // 4. Return authenticated context with pubkey
    Ok(AuthContext::new(event.pubkey))
}

```

This strict validation prevents common attacks including signature replay across different relays and challenge reuse within the same session.

## Relay Integration and Access Control

The `buzz-relay` crate integrates authentication into the connection lifecycle through [`buzz-relay/src/handlers/auth.rs`](https://github.com/block/buzz/blob/main/buzz-relay/src/handlers/auth.rs). After verification, the relay associates the public key with the WebSocket connection in [`buzz-relay/src/connection.rs`](https://github.com/block/buzz/blob/main/buzz-relay/src/connection.rs).

All subsequent operations check this authentication state before execution:

- **Event publishing**: `send_event` validates the connection has an authenticated pubkey
- **Subscription filtering**: `REQ` and `COUNT` commands check `AuthContext` for protected filters
- **Administrative actions**: Channel moderation requires valid authentication

The connection state tracks both the authenticated public key and timeout values, automatically expiring sessions after periods of inactivity to maintain security.

## Summary

- Block Buzz implements **NIP-42 WebSocket authentication** as a challenge-response protocol requiring cryptographic proof of key ownership.
- The client library (`buzz-ws-client`) automatically handles challenge reception, event construction (kind 22242), and transmission via `NostrWsConnection::authenticate`.
- Server verification ([`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs)) enforces Schnorr signature validation, challenge freshness (≤ 1024 bytes), and strict host-binding to prevent relay spoofing.
- The relay (`buzz-relay`) maintains authentication state across the session, gating sensitive operations behind verified `AuthContext` checks.
- Optional **NIP-OA** tokens can be included in AUTH events for additional OAuth-style authorization layers.

## Frequently Asked Questions

### What is NIP-42 and why is it used in Block Buzz?

NIP-42 is a Nostr protocol extension that defines **WebSocket authentication** using signed events rather than static tokens. Block Buzz uses this standard to ensure that only individuals with access to specific private keys can publish events or access restricted relays, providing cryptographic certainty of identity without requiring passwords or API keys.

### How does Block Buzz prevent replay attacks in WebSocket authentication?

Block Buzz prevents replay attacks through **challenge freshness** and **host-binding**. Each connection receives a unique random challenge that must be included in the signed AUTH event, and the event must include the specific relay URL (verified in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs)). This binds the signature to both a specific session and a specific relay, making intercepted AUTH events useless for replay attacks on other connections or relays.

### What happens if the relay URL doesn't match the event's relay tag?

If the `relay` tag in the AUTH event (kind 22242) does not match the host of the connection URL, the `verify_nip42_auth_event` function in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs) returns a **Host Mismatch** error. This verification (specified in NIP-42 row 44) ensures that authentication credentials cannot be stolen and reused across different relay infrastructure, maintaining strict per-relay security boundaries.

### Can Block Buzz authenticate clients using OAuth tokens alongside NIP-42?

Yes, Block Buzz supports **NIP-OA** (Optional OAuth) tags within NIP-42 AUTH events. When building the AUTH event in [`buzz-ws-client/src/message.rs`](https://github.com/block/buzz/blob/main/buzz-ws-client/src/message.rs), clients can include an `auth` tag containing a bearer token. The server-side verification in [`buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/buzz-auth/src/nip42.rs) validates this token if present, allowing hybrid authentication that combines Nostr cryptographic identity with traditional OAuth authorization schemes for additional access control layers.