summaryrefslogtreecommitdiffstats
path: root/internal/auth/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'internal/auth/README.md')
-rw-r--r--internal/auth/README.md181
1 files changed, 181 insertions, 0 deletions
diff --git a/internal/auth/README.md b/internal/auth/README.md
new file mode 100644
index 0000000..adfe260
--- /dev/null
+++ b/internal/auth/README.md
@@ -0,0 +1,181 @@
1# Nostr HTTP Authentication (NIP-98)
2
3This package implements [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) authentication for gRPC using the standard `credentials.PerRPCCredentials` interface.
4
5## Overview
6
7NIP-98 provides HTTP authentication using Nostr event signatures instead of bearer tokens or OAuth2. It uses cryptographic signatures to prove the request came from a specific public key, without requiring passwords or centralized identity providers.
8
9## How It Works
10
11### Authentication Flow
12
131. **Client** creates a special kind 27235 event with:
14 - `u` tag: Full request URI
15 - `method` tag: HTTP method (GET, POST, etc.) or gRPC method name
16 - `payload` tag (optional): SHA256 hash of the request body
17 - `created_at`: Current Unix timestamp
18
192. **Client** signs the event with their private key
20
213. **Client** base64-encodes the event JSON and sends it in the `Authorization` header:
22 ```
23 Authorization: Nostr <base64-encoded-event>
24 ```
25
264. **Server** validates the event:
27 - Verifies the signature matches the pubkey
28 - Checks the timestamp is recent (prevents replay attacks)
29 - Verifies the `u` and `method` tags match the actual request
30 - Optionally validates the payload hash
31
325. **Server** adds the validated pubkey to the request context for use by handlers
33
34### Example Event
35
36```json
37{
38 "id": "9e1b6471f...",
39 "pubkey": "79be667ef9dc...",
40 "created_at": 1682327852,
41 "kind": 27235,
42 "tags": [
43 ["u", "https://api.example.com/nostr.v1.NostrRelay/PublishEvent"],
44 ["method", "POST"],
45 ["payload", "5c9e3a4d..."]
46 ],
47 "content": "",
48 "sig": "d2d6e9f0..."
49}
50```
51
52## Usage
53
54### Client Side
55
56Use the `NostrCredentials` type with standard gRPC dial options:
57
58```go
59import (
60 "google.golang.org/grpc"
61 "northwest.io/muxstr/internal/auth"
62 "northwest.io/muxstr/internal/nostr"
63)
64
65// Generate or load your private key
66key, _ := nostr.GenerateKey()
67
68// Create credentials
69creds := auth.NewNostrCredentials(key)
70
71// Use with gRPC client
72conn, err := grpc.NewClient(
73 "localhost:50051",
74 grpc.WithPerRPCCredentials(creds),
75 grpc.WithTransportCredentials(insecure.NewCredentials()),
76)
77```
78
79The credentials automatically sign each request with a fresh NIP-98 event.
80
81### Server Side
82
83Use the interceptors to validate incoming requests:
84
85```go
86import (
87 "google.golang.org/grpc"
88 "northwest.io/muxstr/internal/auth"
89)
90
91// Create auth options
92authOpts := &auth.InterceptorOptions{
93 TimestampWindow: 60, // Accept events within 60 seconds
94 Required: true, // Reject unauthenticated requests
95}
96
97// Create gRPC server with interceptors
98server := grpc.NewServer(
99 grpc.UnaryInterceptor(auth.NostrUnaryInterceptor(authOpts)),
100 grpc.StreamInterceptor(auth.NostrStreamInterceptor(authOpts)),
101)
102```
103
104### Accessing the Authenticated Pubkey
105
106In your handlers, retrieve the authenticated pubkey from the context:
107
108```go
109func (s *Server) PublishEvent(ctx context.Context, req *pb.PublishEventRequest) (*pb.PublishEventResponse, error) {
110 pubkey, ok := auth.PubkeyFromContext(ctx)
111 if !ok {
112 return nil, status.Error(codes.Unauthenticated, "no authentication")
113 }
114
115 // Verify the event was signed by the authenticated user
116 if req.Event.Pubkey != pubkey {
117 return nil, status.Error(codes.PermissionDenied, "event pubkey doesn't match auth")
118 }
119
120 // Process the event...
121}
122```
123
124## Security Considerations
125
126### Replay Protection
127
128Events include a `created_at` timestamp. The server validates that events are recent (within the configured `TimestampWindow`). This prevents replay attacks where an attacker intercepts and re-sends a valid auth event.
129
130### Transport Security
131
132While NIP-98 provides authentication (proving who you are), it doesn't provide encryption. Use TLS/SSL to encrypt the connection and prevent eavesdropping.
133
134```go
135// Client with TLS
136creds := credentials.NewClientTLSFromCert(nil, "")
137conn, err := grpc.NewClient(addr,
138 grpc.WithTransportCredentials(creds),
139 grpc.WithPerRPCCredentials(nostrCreds),
140)
141```
142
143### Payload Validation
144
145The `payload` tag is optional but recommended for POST/PUT requests. When present, the server can verify the request body hasn't been tampered with:
146
147```go
148authOpts := &auth.InterceptorOptions{
149 ValidatePayload: true, // Verify payload hash if present
150}
151```
152
153## Configuration Options
154
155### InterceptorOptions
156
157- **`TimestampWindow`**: Maximum age of events in seconds (default: 60)
158- **`Required`**: Whether to reject unauthenticated requests (default: false)
159- **`ValidatePayload`**: Whether to verify payload hash when present (default: false)
160- **`AllowedPubkeys`**: Optional whitelist of allowed pubkeys (nil = allow all)
161
162### NostrCredentials Options
163
164- **`IncludePayload`**: Whether to include payload hash in auth events (default: false)
165
166## Benefits Over Traditional Auth
167
1681. **No passwords**: Uses public key cryptography
1692. **Decentralized**: No central identity provider
1703. **Per-request auth**: Each request is independently authenticated
1714. **Nostr compatible**: Works with existing Nostr identities and tools
1725. **Standard pattern**: Uses industry-standard gRPC credentials interface
1736. **Key rotation**: Easy to rotate keys without server-side updates
174
175## Compatibility
176
177This implementation follows the gRPC `credentials.PerRPCCredentials` interface, making it a drop-in replacement for OAuth2, JWT, or other auth mechanisms. It works with:
178
179- Standard gRPC clients (Go, Python, JS, etc.)
180- gRPC-Web and Connect protocol
181- All gRPC features (unary, streaming, metadata, etc.)