Chapter 98 - Auth
98
Nostr Implementation Possibility - Hex № 98

HTTP Auth

Sign in to any HTTP service with your Nostr key: one ephemeral event, base64-encoded, inside the Authorization header.

1 Overview

Many HTTP services are built for Nostr and deal with Nostr user accounts. A media host, an API gateway, or a paid feed needs to know who sends each request. Passwords and API keys would add a second identity next to the user's Nostr key. This NIP removes that second identity.

The client signs a small ephemeral event of kind 27235 that names the exact URL and HTTP method of the request. The client encodes the event as base64 and puts it in the Authorization header. The server decodes the event, verifies it, and knows the caller's pubkey. No account creation, no session, no cookie.

The event travels inside the HTTP request. It normally never touches a relay. Because the kind sits in the ephemeral range of NIP-01, relays would not store it anyway.

Hint!

The kind number 27235 is a wink at RFC 7235, the IETF standard for HTTP authentication. Do not confuse this NIP with NIP-42: NIP-42 authenticates a client to a relay over WebSocket. NIP-98 authenticates a client to an HTTP server.

Event 27235 u + method tags Sign Schnorr base64 encode JSON Header Nostr scheme
Fig. 98-1 — Header pipeline. Build the event, sign it, base64-encode it, and place it after the Nostr scheme.

2 The Auth Event

The auth event is a normal NIP-01 event with kind 27235. The content SHOULD be empty. Two tags are mandatory. One tag is conditional:

Tag reference - kind 27235
TagRequiredRule
uMUSTThe absolute URL of the request, including query parameters.
methodMUSTThe HTTP request method, for example GET or POST.
payloadSHOULD, when the request has a bodyThe sha256 hash of the request body as lowercase hex: ["payload", "<sha256-hex>"]. Use it for POST, PUT, and PATCH requests.
Wire format - kind 27235 auth event
{
  "id": "fe964e758903360f28d8424d092da8494ed207cba823110be3a57dfe4b578734",
  "pubkey": "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
  "content": "",
  "kind": 27235,
  "created_at": 1682327852,
  "tags": [
    ["u", "https://api.snort.social/api/v1/n5sp/list"],
    ["method", "GET"]
  ],
  "sig": "5ed9d8ec958bc854f997bdc24ac337d005af372324747efe4a00e24f4c30437f…"
}

3 Server Validation

The server MUST perform these checks, in order, to validate the event:

  1. The kind MUST be 27235.
  2. The created_at timestamp MUST be within a reasonable time window. The suggestion is 60 seconds.
  3. The u tag MUST be exactly the same as the absolute request URL, including query parameters.
  4. The method tag MUST be the same HTTP method used for the requested resource.

If one of the checks fails, the server SHOULD respond with a 401 Unauthorized status code. When the request carries a payload tag, the server MAY hash the request body and compare it to the tag value. This validates that the caller authorized this exact payload. The server MAY also perform additional implementation-specific checks.

Warning!

The u match is exact, byte for byte. One reordered query parameter, a trailing slash, or an http instead of https makes the check fail. Clients: build the event from the final URL you send. Servers behind proxies: reconstruct the URL the client actually used.

Tech Note

The ordered check list in the source never says to verify the event id or the Schnorr sig. It is implied by NIP-01, but a literal reading lets a forged header pass. Servers must verify the signature before they trust the pubkey. The source also gives no rule for URL normalization (case, port, encoding) — implementations must agree on the exact string.

4 Request Flow

The event travels in the Authorization HTTP header. The header MUST use the Authorization scheme Nostr. The value MUST be the base64-encoded JSON of the kind 27235 event. The whole header is one line:

HTTP request header
Authorization: Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIz…
(base64 of the full event JSON, shortened here for print)
Client HTTP Server sign kind 27235 GET /api/v1/n5sp/list - Authorization: Nostr <base64> decode - verify sig check kind, time, u, method 200 OK - body for pubkey 63fe6318… 401 Unauthorized - if any check fails The event is single-use in practice: the 60-second window expires it fast.
Fig. 98-2 — Auth round trip. One signed event authorizes one HTTP request. The server answers 200 on success, 401 on any failed check.

5 Worked Example

A client uploads a report to a Nostr-aware API with POST. The request has a body, so the client adds a payload tag with the sha256 hash of the body. Then it signs, encodes, and sends:

Step 1 - build and sign the auth event
{
  "pubkey": "63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed",
  "content": "",
  "kind": 27235,
  "created_at": 1682328000,
  "tags": [
    ["u", "https://api.example.com/v1/reports?draft=false"],
    ["method", "POST"],
    ["payload", "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"]
  ],
  "id": <sha256 of the serialized event>,
  "sig": <Schnorr signature>
}
Step 2 - HTTP session transcript
-- client sends the request --
→ POST /v1/reports?draft=false HTTP/1.1
→ Host: api.example.com
→ Authorization: Nostr eyJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRk…
→ Content-Type: application/json
→
→ {"title":"weekly report"}

-- server decodes, verifies sig, runs the 4 checks, hashes the body --
← HTTP/1.1 200 OK
← {"status":"stored","owner":"63fe6318…"}

-- 90 seconds later the client replays the same header --
← HTTP/1.1 401 Unauthorized
← {"error":"auth event expired"}

A reference implementation exists for C# ASP.NET: the NostrAuth.cs AuthenticationHandler gist by the NIP's author.

6 Version History

NIP-98 arrived in one piece in April 2023 and has changed little since. Notable changes, newest first:

  • 20232023-12Fixed the Authorization header example string.
  • 20232023-11Repo-wide cleanups: JSON reformatting and removal of the NIP author lines.
  • 20232023-09Spelling fixes and clarifications to the validation wording.
  • 20232023-05Typo fixes; the references section was retitled.
  • 20232023-04Added the payload tag: a sha256 hash of the request body for methods that carry one.
  • Origin2023-04-24Kieran published the NIP: kind 27235, the u and method tags, and the Nostr Authorization scheme.

Original Proposal - Credits

AuthorKieran
First commit2023-04-24 — “NIP-98”
Statusdraft - optional
Source98.md on GitHub - full change history

7 See Also

  • NIP-01 — the event format, serialization, signatures, and the ephemeral kind range that 27235 lives in.
  • NIP-42 — the WebSocket counterpart: authentication of clients to relays.
  • NIP-96 — HTTP file storage; its servers use NIP-98 events to authorize uploads.