# Buzz-Auth Functionalities: Handling NIP-42, NIP-98, API Tokens, and Rate Limiting

> Explore buzz-auth functionalities for Nostr relays. Learn how it handles NIP-42, NIP-98, API tokens, and rate limiting to secure your operations.

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

---

**buzz-auth** is the security core of the Buzz relay that validates Nostr-based NIP-42 and NIP-98 authentication schemes and provides a generic rate-limiting interface for protecting PubKey-scoped and IP-scoped operations.

The `buzz-auth` crate serves as the authentication and security foundation for Block's Buzz Nostr relay implementation. It handles cryptographic verification of Nostr Identity Protocol (NIP) events, manages API token validation, and implements a flexible rate-limiting system. Understanding the **functionalities of buzz-auth in handling NIP-42, NIP-98, API tokens, and rate limiting** is essential for developers integrating with or operating Buzz infrastructure.

## NIP-42 Challenge-Response Authentication

Located in [`crates/buzz-auth/src/nip42.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/nip42.rs), buzz-auth implements the NIP-42 protocol for WebSocket session authentication using signed `kind = 22242` events.

### Challenge Generation

The `generate_challenge()` function creates a cryptographically secure 32-byte random hex string. This challenge is transmitted to the client, who must include it in a signed authentication event to prove ownership of their private key.

### Event Verification

The `verify_nip42_event()` function validates the client's signed response against the server-generated challenge. According to the Block buzz source code, this verification includes:

- Confirming the event kind is `Authentication` (22242)
- Verifying the Schnorr signature via `buzz_core::verify_event`
- Matching the challenge tag against the server-generated challenge
- Normalizing and comparing the relay URL tag (treating `localhost` and `127.0.0.1` as equivalent)
- Checking the timestamp is within ±`TIMESTAMP_TOLERANCE_SECS` (60 seconds)

Verification failures return specific `AuthError` variants defined in [`crates/buzz-auth/src/error.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/error.rs): `InvalidSignature`, `ChallengeMismatch`, `RelayUrlMismatch`, or `EventExpired`.

```rust
let challenge = buzz_auth::generate_challenge();
// Client signs an AUTH event (kind 22242) containing the challenge
// Server verification:
let ok = buzz_auth::verify_nip42_event(&event, &challenge, "wss://relay.example.com");

```

## NIP-98 HTTP Bearer Token Authentication

The [`crates/buzz-auth/src/nip98.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/nip98.rs) module implements NIP-98 for stateless HTTP authentication, validating base64-encoded bearer tokens containing `kind = 27235` events.

### Bearer Token Structure

The system expects an `Authorization` header formatted as `Nostr <base64>`, where the payload is a JSON-encoded Nostr event of kind `HttpAuth` (27235).

### Verification Logic

The `verify_nip98_event(event_json, expected_url, expected_method, body)` function executes a six-step validation pipeline:

1. Confirms `kind == HttpAuth` (27235)
2. Verifies the Schnorr signature using `buzz_core::verify_event`
3. Validates `created_at` is within ±60 seconds of current time
4. Normalizes and compares the `u` tag (URL) against `expected_url`
5. Performs case-insensitive comparison of the `method` tag against `expected_method`
6. If a `payload` tag exists and request body is provided, validates that SHA-256(body) matches the hex payload

On success, the function returns the authenticated public key. Failures yield `AuthError::Nip98Invalid` with safe diagnostic messaging that avoids leaking sensitive implementation details.

```rust
let auth_header = req.headers().get("Authorization").unwrap(); // "Nostr <base64>"
let event_json = base64::decode(&auth_header[6..]).unwrap();
let pubkey = buzz_auth::verify_nip98_event(
    &event_json,
    "https://api.example.com/submit",
    "POST",
    Some(req.body_bytes()),
)?;

```

## Rate Limiting Interface

Defined in [`crates/buzz-auth/src/rate_limit.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/rate_limit.rs), the rate-limiting system provides a pluggable trait architecture for throttling both authenticated users and raw IP connections.

### RateLimiter Trait and Configuration

The `RateLimiter` trait defines two async methods:

- **`check_and_increment`**: PubKey-scoped, community-aware limits for authenticated operations
- **`check_ip_connection`**: Global IP-scoped connection limits

`RateLimitConfig` holds per-tier limits distinguishing between human users and agent tokens. `LimitType` enumerates rate-limited categories (messages, API calls, GIF searches, WebSocket events, and IP connections) and provides Redis key suffixes (`msg`, `api`, `gif`, `ws`, `conn`).

### Redis Key Scoping

Helper functions construct scoped Redis keys:

- **PubKey limits**: `buzz:{community}:ratelimit:{pubkey}:{suffix}` (community-scoped)
- **IP limits**: Global scope via `ip_rate_limit_key`

`RateLimitResult` returns whether the request is allowed, the current counter, the configured limit, and seconds until the window resets. A no-op `AlwaysAllowRateLimiter` implementation is provided in [`crates/buzz-auth/src/lib.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/lib.rs) for unit testing scenarios.

```rust
let limiter = buzz_auth::AlwaysAllowRateLimiter; // test stub
let ctx = tenant_context(); // resolved community
let result = limiter
    .check_and_increment(&ctx, &pubkey, buzz_auth::LimitType::Messages, 60, 60)
    .await?;
if !result.allowed {
    // Return 429 Too Many Requests
}

```

## Error Handling

[`crates/buzz-auth/src/error.rs`](https://github.com/block/buzz/blob/main/crates/buzz-auth/src/error.rs) centralizes error handling through the `AuthError` enum, used by both NIP-42 and NIP-98 verification modules. This ensures consistent error propagation and safe messaging across the authentication boundary without exposing internal validation logic.

## Summary

- **NIP-42 Authentication**: Implements challenge-response WebSocket authentication via `generate_challenge()` and `verify_nip42_event()`, validating kind 22242 events with 60-second timestamp tolerance and relay URL normalization.
- **NIP-98 Authentication**: Validates HTTP bearer tokens containing base64-encoded kind 27235 events, verifying signatures, timestamps, URL/method tags, and optional SHA-256 payload hashes.
- **API Token Handling**: Both protocols rely on `buzz_core::verify_event` for Schnorr signature verification and return the authenticated public key upon success.
- **Rate Limiting**: Provides a `RateLimiter` trait with `check_and_increment` for community-scoped PubKey limits and `check_ip_connection` for global IP throttling, using Redis keys formatted as `buzz:{community}:ratelimit:{pubkey}:{suffix}`.

## Frequently Asked Questions

### What distinguishes NIP-42 from NIP-98 authentication in buzz-auth?

NIP-42 provides **session-based** authentication for WebSocket connections using challenge-response mechanics with ephemeral challenges generated by `generate_challenge()`, while NIP-98 offers **stateless** HTTP authentication where each request carries a self-contained bearer token (kind 27235) that the server validates independently without maintaining session state.

### How does buzz-auth implement rate limiting for different user tiers?

The `RateLimitConfig` struct defines per-tier limits distinguishing human users from automated agents. The `check_and_increment` method on the `RateLimiter` trait accepts these configuration parameters to enforce different thresholds based on the authenticated PubKey's assigned tier, while `LimitType` categorizes operations (messages, API calls, GIF searches) for granular control.

### What validation occurs when a NIP-42 challenge expires?

The `verify_nip42_event()` function checks the event's `created_at` timestamp against the current time with a tolerance of `TIMESTAMP_TOLERANCE_SECS` (60 seconds). If the event falls outside this window, the function returns `AuthError::EventExpired`, rejecting the authentication attempt and requiring the client to request a fresh challenge.

### Can buzz-auth operate without a Redis backend for rate limiting?

Yes. The crate provides the `AlwaysAllowRateLimiter` no-op implementation that satisfies the `RateLimiter` trait by always returning `allowed: true`, enabling unit testing and development environments to function without Redis infrastructure. Production deployments typically implement the trait with Redis-backed storage using the `rate_limit_key` and `ip_rate_limit_key` helpers.