clone

every nostr nip as a chapter of an illustrated, early-2000s printed game manual

main
nip.guide / site / nips / 98.html
98.html 17.7K
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-98 - HTTP Auth — Nostr Instruction Manual</title>7<meta name="description" content="NIP-98: HTTP Auth. Authorize HTTP requests with a signed kind 27235 event carried in the Authorization header. Complete manual page.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 98 - Auth</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="98">98</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 98</div>50 <h1>HTTP Auth</h1>51 <p class="sub">Sign in to any HTTP service with your Nostr key: one ephemeral event, base64-encoded, inside the Authorization header.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>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.</p>60 <p>The client signs a small <strong>ephemeral event</strong> of <code>kind 27235</code> that names the exact URL and HTTP method of the request. The client encodes the event as base64 and puts it in the <code>Authorization</code> header. The server decodes the event, verifies it, and knows the caller's <code>pubkey</code>. No account creation, no session, no cookie.</p>61 <p>The event travels inside the HTTP request. It normally never touches a relay. Because the kind sits in the ephemeral range of <a href="01.html">NIP-01</a>, relays would not store it anyway.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>The kind number 27235 is a wink at <a href="https://www.rfc-editor.org/rfc/rfc7235">RFC 7235</a>, the IETF standard for HTTP authentication. Do not confuse this NIP with <a href="42.html">NIP-42</a>: NIP-42 authenticates a client to a <em>relay</em> over WebSocket. NIP-98 authenticates a client to an <em>HTTP server</em>.</p>66 </div>6768 <figure class="diagram">69 <div class="frame">70 <svg viewBox="0 0 560 120" role="img" aria-label="A kind 27235 event is signed, base64 encoded, placed in the Authorization header, and sent with the HTTP request">71 <rect class="d-actor" x="10" y="35" width="120" height="50" rx="8"/>72 <text class="d-actor-label" x="70" y="57" text-anchor="middle">Event 27235</text>73 <text class="d-note" x="70" y="74" text-anchor="middle">u + method tags</text>74 <rect class="d-actor" x="180" y="35" width="90" height="50" rx="8"/>75 <text class="d-actor-label" x="225" y="57" text-anchor="middle">Sign</text>76 <text class="d-note" x="225" y="74" text-anchor="middle">Schnorr</text>77 <rect class="d-actor" x="320" y="35" width="90" height="50" rx="8"/>78 <text class="d-actor-label" x="365" y="57" text-anchor="middle">base64</text>79 <text class="d-note" x="365" y="74" text-anchor="middle">encode JSON</text>80 <rect class="d-actor" x="460" y="35" width="90" height="50" rx="8"/>81 <text class="d-actor-label" x="505" y="57" text-anchor="middle">Header</text>82 <text class="d-note" x="505" y="74" text-anchor="middle">Nostr scheme</text>83 <path class="d-msg d-flow" d="M132,60 L177,60"/>84 <path class="d-msg d-flow" d="M272,60 L317,60"/>85 <path class="d-msg d-flow" d="M412,60 L457,60"/>86 </svg>87 </div>88 <figcaption>Fig. 98-1 — <b>Header pipeline.</b> Build the event, sign it, base64-encode it, and place it after the <code>Nostr</code> scheme.</figcaption>89 </figure>9091 <h2 class="bar" id="spec"><span class="num">2</span> The Auth Event</h2>92 <p>The auth event is a normal <a href="01.html">NIP-01</a> event with <code>kind 27235</code>. The <code>content</code> SHOULD be empty. Two tags are mandatory. One tag is conditional:</p>9394 <div class="tscroll"><table class="ref">95 <caption>Tag reference - kind 27235</caption>96 <tr><th>Tag</th><th>Required</th><th>Rule</th></tr>97 <tr><td class="btn">u</td><td>MUST</td><td>The absolute URL of the request, including query parameters.</td></tr>98 <tr><td class="btn">method</td><td>MUST</td><td>The HTTP request method, for example <code>GET</code> or <code>POST</code>.</td></tr>99 <tr><td class="btn">payload</td><td>SHOULD, when the request has a body</td><td>The sha256 hash of the request body as lowercase hex: <code>["payload", "&lt;sha256-hex&gt;"]</code>. Use it for <code>POST</code>, <code>PUT</code>, and <code>PATCH</code> requests.</td></tr>100 </table></div>101102 <div class="crt">103 <div class="titlebar">Wire format - kind 27235 auth event</div>104<pre>{105 <span class="k">"id"</span>: <span class="s">"fe964e758903360f28d8424d092da8494ed207cba823110be3a57dfe4b578734"</span>,106 <span class="k">"pubkey"</span>: <span class="s">"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"</span>,107 <span class="k">"content"</span>: <span class="s">""</span>,108 <span class="k">"kind"</span>: 27235,109 <span class="k">"created_at"</span>: 1682327852,110 <span class="k">"tags"</span>: [111 [<span class="s">"u"</span>, <span class="s">"https://api.snort.social/api/v1/n5sp/list"</span>],112 [<span class="s">"method"</span>, <span class="s">"GET"</span>]113 ],114 <span class="k">"sig"</span>: <span class="s">"5ed9d8ec958bc854f997bdc24ac337d005af372324747efe4a00e24f4c30437f…"</span>115}</pre>116 </div>117118 <h2 class="bar" id="validation"><span class="num">3</span> Server Validation</h2>119 <p>The server MUST perform these checks, in order, to validate the event:</p>120 <ol>121 <li>The <code>kind</code> MUST be <code>27235</code>.</li>122 <li>The <code>created_at</code> timestamp MUST be within a reasonable time window. The suggestion is 60 seconds.</li>123 <li>The <code>u</code> tag MUST be exactly the same as the absolute request URL, including query parameters.</li>124 <li>The <code>method</code> tag MUST be the same HTTP method used for the requested resource.</li>125 </ol>126 <p>If one of the checks fails, the server SHOULD respond with a <code>401 Unauthorized</code> status code. When the request carries a <code>payload</code> 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.</p>127128 <div class="box warn">129 <span class="tag">Warning!</span>130 <p>The <code>u</code> match is exact, byte for byte. One reordered query parameter, a trailing slash, or an <code>http</code> instead of <code>https</code> makes the check fail. Clients: build the event from the final URL you send. Servers behind proxies: reconstruct the URL the client actually used.</p>131 </div>132133 <div class="box tech">134 <span class="tag">Tech Note</span>135 <p>The ordered check list in the source never says to verify the event <code>id</code> or the Schnorr <code>sig</code>. It is implied by <a href="01.html">NIP-01</a>, but a literal reading lets a forged header pass. Servers must verify the signature before they trust the <code>pubkey</code>. The source also gives no rule for URL normalization (case, port, encoding) — implementations must agree on the exact string.</p>136 </div>137138 <h2 class="bar" id="flow"><span class="num">4</span> Request Flow</h2>139 <p>The event travels in the <code>Authorization</code> HTTP header. The header MUST use the Authorization scheme <code>Nostr</code>. The value MUST be the base64-encoded JSON of the <code>kind 27235</code> event. The whole header is one line:</p>140141 <div class="crt">142 <div class="titlebar">HTTP request header</div>143<pre><span class="k">Authorization:</span> <span class="s">Nostr eyJpZCI6ImZlOTY0ZTc1ODkwMzM2MGYyOGQ4NDI0ZDA5MmRhODQ5NGVkMjA3Y2JhODIz…</span>144<span class="c">(base64 of the full event JSON, shortened here for print)</span></pre>145 </div>146147 <figure class="diagram">148 <div class="frame">149 <svg viewBox="0 0 560 300" role="img" aria-label="Sequence diagram: client signs a kind 27235 event, sends it in the Authorization header, the server validates and answers 200 or 401">150 <line class="d-wire" x1="110" y1="40" x2="110" y2="285" stroke-dasharray="3 4"/>151 <line class="d-wire" x1="450" y1="40" x2="450" y2="285" stroke-dasharray="3 4"/>152 <rect class="d-actor" x="55" y="8" width="110" height="30" rx="6"/>153 <text class="d-actor-label" x="110" y="28" text-anchor="middle">Client</text>154 <rect class="d-actor" x="395" y="8" width="110" height="30" rx="6"/>155 <text class="d-actor-label" x="450" y="28" text-anchor="middle">HTTP Server</text>156 <text class="d-note" x="110" y="62" text-anchor="middle">sign kind 27235</text>157 <path class="d-msg d-flow" d="M110,95 L445,95"/>158 <text class="d-label" x="280" y="88" text-anchor="middle">GET /api/v1/n5sp/list - Authorization: Nostr &lt;base64&gt;</text>159 <text class="d-note" x="450" y="130" text-anchor="middle">decode - verify sig</text>160 <text class="d-note" x="450" y="147" text-anchor="middle">check kind, time, u, method</text>161 <path class="d-msg blue" d="M450,180 L115,180"/>162 <text class="d-label" x="280" y="173" text-anchor="middle">200 OK - body for pubkey 63fe6318…</text>163 <path class="d-msg blue" d="M450,235 L115,235"/>164 <text class="d-label" x="280" y="228" text-anchor="middle">401 Unauthorized - if any check fails</text>165 <text class="d-note" x="280" y="270" text-anchor="middle">The event is single-use in practice: the 60-second window expires it fast.</text>166 <circle r="4" fill="#f26a00">167 <animateMotion dur="2.6s" repeatCount="indefinite" path="M110,95 L445,95"/>168 </circle>169 </svg>170 </div>171 <figcaption>Fig. 98-2 — <b>Auth round trip.</b> One signed event authorizes one HTTP request. The server answers 200 on success, 401 on any failed check.</figcaption>172 </figure>173174 <h2 class="bar" id="examples"><span class="num">5</span> Worked Example</h2>175 <p>A client uploads a report to a Nostr-aware API with <code>POST</code>. The request has a body, so the client adds a <code>payload</code> tag with the sha256 hash of the body. Then it signs, encodes, and sends:</p>176177 <div class="crt">178 <div class="titlebar">Step 1 - build and sign the auth event</div>179<pre>{180 <span class="k">"pubkey"</span>: <span class="s">"63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"</span>,181 <span class="k">"content"</span>: <span class="s">""</span>,182 <span class="k">"kind"</span>: 27235,183 <span class="k">"created_at"</span>: 1682328000,184 <span class="k">"tags"</span>: [185 [<span class="s">"u"</span>, <span class="s">"https://api.example.com/v1/reports?draft=false"</span>],186 [<span class="s">"method"</span>, <span class="s">"POST"</span>],187 [<span class="s">"payload"</span>, <span class="s">"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"</span>]188 ],189 <span class="k">"id"</span>: <span class="c">&lt;sha256 of the serialized event&gt;</span>,190 <span class="k">"sig"</span>: <span class="c">&lt;Schnorr signature&gt;</span>191}</pre>192 </div>193194 <div class="crt">195 <div class="titlebar">Step 2 - HTTP session transcript</div>196<pre><span class="c">-- client sends the request --</span>197<span class="send">→ POST /v1/reports?draft=false HTTP/1.1</span>198<span class="send">→ Host: api.example.com</span>199<span class="send">→ Authorization: Nostr eyJwdWJrZXkiOiI2M2ZlNjMxOGRjNTg1ODNjZmUxNjgxMGY4NmRk…</span>200<span class="send">→ Content-Type: application/json</span>201<span class="send">→</span>202<span class="send">→ {"title":"weekly report"}</span>203204<span class="c">-- server decodes, verifies sig, runs the 4 checks, hashes the body --</span>205<span class="recv">← HTTP/1.1 200 OK</span>206<span class="recv">← {"status":"stored","owner":"63fe6318…"}</span>207208<span class="c">-- 90 seconds later the client replays the same header --</span>209<span class="recv">← HTTP/1.1 401 Unauthorized</span>210<span class="recv">← {"error":"auth event expired"}</span></pre>211 </div>212213 <p>A reference implementation exists for C# ASP.NET: the <a href="https://gist.github.com/v0l/74346ae530896115bfe2504c8cd018d3">NostrAuth.cs</a> <code>AuthenticationHandler</code> gist by the NIP's author.</p>214215 <h2 class="bar" id="history"><span class="num">6</span> Version History</h2>216 <p>NIP-98 arrived in one piece in April 2023 and has changed little since. Notable changes, newest first:</p>217 <ul class="patchnotes">218 <li><span class="ver">2023</span><span class="when">2023-12</span>Fixed the Authorization header example string.</li>219 <li class="minor"><span class="ver">2023</span><span class="when">2023-11</span>Repo-wide cleanups: JSON reformatting and removal of the NIP author lines.</li>220 <li><span class="ver">2023</span><span class="when">2023-09</span>Spelling fixes and clarifications to the validation wording.</li>221 <li class="minor"><span class="ver">2023</span><span class="when">2023-05</span>Typo fixes; the references section was retitled.</li>222 <li><span class="ver">2023</span><span class="when">2023-04</span>Added the <code>payload</code> tag: a sha256 hash of the request body for methods that carry one.</li>223 <li><span class="ver">Origin</span><span class="when">2023-04-24</span>Kieran published the NIP: kind 27235, the <code>u</code> and <code>method</code> tags, and the <code>Nostr</code> Authorization scheme.</li>224 </ul>225226 <div class="credits">227 <h3 class="cardhead">Original Proposal - Credits</h3>228 <table>229 <tr><td>Author</td><td>Kieran</td></tr>230 <tr><td>First commit</td><td>2023-04-24 — “NIP-98”</td></tr>231 <tr><td>Status</td><td>draft - optional</td></tr>232 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/98.md">98.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/98.md">full change history</a></td></tr>233 </table>234 </div>235236 <h2 class="bar"><span class="num">7</span> See Also</h2>237 <ul>238 <li><a href="01.html">NIP-01</a> — the event format, serialization, signatures, and the ephemeral kind range that 27235 lives in.</li>239 <li><a href="42.html">NIP-42</a> — the WebSocket counterpart: authentication of clients to relays.</li>240 <li><a href="96.html">NIP-96</a> — HTTP file storage; its servers use NIP-98 events to authorize uploads.</li>241 </ul>242 </div>243244 <aside>245 <div class="cartridge">246 <div class="inner">247 <h3 class="cardhead">Spec Card</h3>248 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>249 <dl>250 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span></dd>251 <dt>Defines</dt><dd>kind 27235 auth event - u/method/payload tags - <code>Nostr</code> Authorization scheme</dd>252 <dt>Kind</dt><dd>27235 (ephemeral)</dd>253 <dt>Requires</dt><dd><a href="01.html">NIP-01</a></dd>254 <dt>Transport</dt><dd>HTTP - Authorization header - base64 JSON</dd>255 <dt>Crypto</dt><dd>BIP-340 Schnorr - sha256 payload hash</dd>256 <dt>First commit</dt><dd>2023-04-24</dd>257 <dt>Last updated</dt><dd>2023-12-08</dd>258 </dl>259 </div>260 </div>261 </aside>262 </main>263264 <nav class="navbtns">265 <a href="96.html">◀ NIP-96: HTTP File Storage Integration</a>266 <a href="99.html">NIP-99: Classified Listings ▶</a>267 </nav>268269 <footer class="pagefoot">270 <a href="../index.html">Nostr Protocol Manual</a>271 <span class="pageno">NIP 98 / F4</span>272 </footer>273</div>274275<script src="../assets/webmcp.js" defer></script>276</body>277</html>