This middleware supports two authentication protocols:
- NIP-07 — Browser extension auth via
window.nostr(existing) - NIP-46 — Remote signer / bunker auth via encrypted kind 24133 events (new in v0.5.0)
The main class for NIP-07 Nostr authentication.
new NostrAuthMiddleware(options: NostrAuthOptions)| Option | Type | Default | Description |
|---|---|---|---|
jwtSecret |
string |
— | Secret for JWT signing (required in production) |
expiresIn |
string |
'24h' |
JWT token expiration time |
Generates a JWT token with minimal claims.
const token = await auth.generateToken({ pubkey: 'npub1...' });Returns: Promise<string> — The signed JWT token containing pubkey, iat, and exp claims.
Verifies a JWT token.
const isValid = await auth.verifyToken(token);Returns: Promise<boolean>
Verifies if a user's session is still valid.
const isValid = await auth.verifySession(userPubkey);Returns: Promise<boolean>
Returns Express middleware that authenticates requests using Nostr.
app.get('/protected', auth.authenticate(), handler);Lightweight browser-based authentication using NIP-07.
new NostrBrowserAuth(options?: BrowserAuthOptions)| Option | Type | Default | Description |
|---|---|---|---|
customKind |
number |
22242 |
Custom event kind for auth |
timeout |
number |
30000 |
Timeout in milliseconds |
Gets the user's public key from their Nostr extension.
const publicKey = await auth.getPublicKey();Signs an authentication challenge.
const challenge = await auth.signChallenge();Validates an existing session.
const isValid = await auth.validateSession(session);Express middleware that acts as a NIP-46 remote signer. Accepts incoming NIP-46 requests (kind 24133 events), dispatches them to consumer-provided handlers, and returns encrypted responses.
new Nip46SignerMiddleware(config: Nip46SignerConfig, handlers: Nip46SignerHandlers)| Option | Type | Default | Description |
|---|---|---|---|
signerSecretKey |
string |
— | Signer's private key (hex). Required. |
signerPubkey |
string |
derived | Signer's public key. Derived from secret key if omitted. |
relays |
string[] |
— | Relay URLs for communication. Required. |
secret |
string |
— | Expected connection secret for bunker:// URIs |
requireAuth |
boolean |
true |
Require connect handshake before other methods |
sessionTimeoutMs |
number |
3600000 |
Session timeout (1 hour default) |
interface Nip46SignerHandlers {
getPublicKey: () => string | Promise<string>;
signEvent: (eventJson: string) => string | Promise<string>;
nip04Encrypt?: (pubkey: string, plaintext: string) => string | Promise<string>;
nip04Decrypt?: (pubkey: string, ciphertext: string) => string | Promise<string>;
nip44Encrypt?: (pubkey: string, plaintext: string) => string | Promise<string>;
nip44Decrypt?: (pubkey: string, ciphertext: string) => string | Promise<string>;
getRelays?: () => string | Promise<string>;
}Receives a kind 24133 event, decrypts the NIP-46 request, dispatches to the appropriate handler, and returns the encrypted response.
Request body: { event: SignedNostrEvent }
Response: { event: SignedNostrEvent } (encrypted NIP-46 response)
Returns signer metadata.
Response:
{
"pubkey": "abc123...",
"relays": ["wss://relay.example.com"],
"supportedMethods": ["connect", "ping", "get_public_key", "sign_event", ...]
}Returns a bunker:// URI for connecting to this signer.
Response: { "bunkerUri": "bunker://abc123...?relay=wss://..." }
Returns the Express router.
app.use('/nip46', signer.getRouter());Returns a Nostr filter for subscribing to incoming NIP-46 events.
const filter = signer.getRequestFilter();
// { kinds: [24133], '#p': ['<signer-pubkey>'] }Cleans up the session cleanup interval and clears all tracked sessions.
import { createNip46Signer } from 'nostr-auth-middleware';
const signer = createNip46Signer(config, handlers);
app.use('/nip46', signer.getRouter());Browser-side handler for authenticating via a NIP-46 remote signer (bunker). Transport-agnostic — you provide relay I/O via the Nip46Transport interface.
new Nip46AuthHandler(config: Nip46AuthConfig)| Option | Type | Default | Description |
|---|---|---|---|
bunkerUri |
string |
— | bunker:// URI (extracts remotePubkey, relays, secret) |
remotePubkey |
string |
— | Remote signer's pubkey (alternative to bunkerUri) |
relays |
string[] |
— | Relay URLs (required if not using bunkerUri) |
secret |
string |
— | Connection secret |
customKind |
number |
22242 |
Event kind for challenge events |
timeout |
number |
30000 |
Timeout for remote signer responses (ms) |
serverUrl |
string |
— | Server URL for challenge/verify endpoints |
permissions |
string |
— | Requested permissions (comma-separated) |
You must provide a transport for relay communication:
interface Nip46Transport {
sendEvent(event: SignedNostrEvent): Promise<void>;
subscribe(
filter: { kinds: number[]; '#p': string[]; since?: number },
onEvent: (event: SignedNostrEvent) => void
): () => void; // returns cleanup function
}Connects to the remote signer. Creates an ephemeral session, sends a connect request, and waits for acknowledgment.
await auth.connect(myTransport);Sets the transport without connecting.
auth.setTransport(myTransport);Full authentication flow: gets pubkey from remote signer, fetches challenge from server, asks signer to sign it, submits to server for verification.
const result = await auth.authenticate();
// { pubkey, signedEvent, sessionInfo: { clientPubkey, remotePubkey }, timestamp }Returns: Promise<Nip46AuthResult>
Pings the remote signer to check if the session is still alive.
const isValid = await auth.validateSession();Returns: Promise<boolean>
Returns current session info or null if not connected.
const info = auth.getSessionInfo();
// { clientPubkey, remotePubkey } or nullDestroys the session and cleans up.
auth.destroy();