Loading repository data…
Loading repository data…
HumanjavaEnterprises / repository
The nostr-auth-middleware repository offers a TypeScript-based middleware solution for managing authentication and enrollment in Nostr web applications. It supports NIP-07 compatible authentication, secure user enrollment, event validation, and JWT-based session management, with optional Supabase integration for data persistence.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
A focused, security-first authentication middleware for Nostr applications. Supports both NIP-07 (browser extension) and NIP-46 (remote signer / bunker) authentication flows.
Release note — v0.6.0 (staged, pending publish). Part of the coordinated 2026-07 correctness pass across the Nostr library family, verified against a shared known-answer vector set (NIP-44 v2 / NIP-49 / NIP-19 TLV / BIP-340). This release adds challenge-binding to close an auth-bypass (see the security note below). The family dogfoods only its own libraries — no upstream
nostr-toolsdependency.
npm install nostr-auth-middleware
Important Security Notice
This library handles cryptographic keys and authentication tokens that are critical for securing your Nostr application and user data. Any private keys (nsec) or authentication tokens must be stored and managed with the utmost security and care.
🔒 Security fix in 0.6.0 (breaking — upgrade immediately). Versions prior to 0.6.0 did not bind the signed challenge event to the issued challenge during
verifyChallenge: the Supabase path accepted any recently-signed event from a target pubkey (any kind, no challenge match), allowing account takeover without the victim's key, and the exportedgenerateChallenge()used a static, replayable challenge value. 0.6.0 enforces the full challenge contract — kind 22242, a['challenge', <issued nonce>]tag, event-id integrity, valid signature, freshness, and an exact nonce match — on both storage branches, andgenerateChallenge()now emits a cryptographically-random nonce. The challenge/verification wire format changed; see the CHANGELOG for migration details.
The server issues a random per-request challenge nonce. To authenticate, the client signs a kind 22242 event that carries the challenge in a ['challenge', <challenge>] tag (the bundled NostrBrowserAuth and Nip46AuthHandler clients do this automatically) and POSTs it to /verify. The server verifies the signature, event-id integrity, kind, and timestamp, and requires the challenge tag to match the exact issued nonce before returning a JWT. Challenges are single-use.
Developers using this middleware must inform their users about the critical nature of managing private keys and tokens. It is the user's responsibility to securely store and manage these credentials. The library and its authors disclaim any responsibility or liability for lost keys, compromised tokens, or data resulting from mismanagement.
import { NostrAuthMiddleware, Nip46SignerMiddleware } from 'nostr-auth-middleware';
const { NostrAuthMiddleware, Nip46SignerMiddleware } = require('nostr-auth-middleware');
<script src="dist/browser/nostr-auth-middleware.min.js"></script>
<script>
// NIP-07 (browser extension)
const auth = new NostrAuthMiddleware.NostrBrowserAuth();
// NIP-46 (remote signer / bunker)
const nip46 = new NostrAuthMiddleware.Nip46AuthHandler({
bunkerUri: 'bunker://...?relay=wss://relay.example.com',
serverUrl: 'https://auth.example.com'
});
</script>
This middleware follows key principles that promote security, auditability, and simplicity:
+------------------+ +------------------+
| Client App | | NIP-46 Bunker |
| (NIP-07 ext or | | (Remote Signer) |
| NIP-46 bunker) | +--------+---------+
+--------+---------+ |
| |
v kind 24133 v
+----------------------------------+
| Nostr Auth Service | <-- This Service
| NIP-07 challenge/verify | Simple Auth Only
| NIP-46 signer middleware |
+----------------+-----------------+
|
v
+------------------+
| App Platform | <-- Your Business Logic
| API | User Tiers
+------------------+ Rate Limits
window.nostr (nos2x, Alby, NostrKey, etc.)const auth = new NostrAuthMiddleware({
jwtSecret: process.env.JWT_SECRET, // Required in production
expiresIn: '24h' // Optional, defaults to '24h'
});
// Generate a JWT token with minimal claims
const token = await auth.generateToken({ pubkey });
// The generated token includes:
// - pubkey (npub)
// - iat (issued at timestamp)
// - exp (expiration timestamp)
// Verify a JWT token
const isValid = await auth.verifyToken(token);
if (isValid) {
// Token is valid, proceed with authentication
}
The middleware is fully compatible with modern browsers and works seamlessly with build tools like Vite, Webpack, and Rollup. When using in a browser environment:
import { NostrAuthMiddleware } from 'nostr-auth-middleware';
const auth = new NostrAuthMiddleware({
jwtSecret: import.meta.env.VITE_JWT_SECRET, // For Vite
// or
jwtSecret: process.env.REACT_APP_JWT_SECRET // For Create React App
});
JWT Secret Management
Token Storage
Environment Variables
// Verify if a user's session is still valid
const isValid = await auth.verifySession(userPubkey);
if (isValid) {
console.log('Session is valid');
} else {
console.log('Session is invalid or expired');
// Handle logout
}
The session verification:
When running in development mode, the middleware provides detailed logging:
// Development mode logs
Cached Profile: { /* profile data */ }
Fresh Profile: { /* profile and event data */ }
Profile Cache Hit: { pubkey, cacheAge }
Profile Cache Expired: { pubkey, cacheAge }
Profile Cached: { pubkey, profile }
Profile Cache Cleared: { pubkey }
For browser-specific TypeScript declarations, we follow a top-level pattern that avoids module augmentation blocks:
// Define interfaces and types at top level
interface NostrAuthConfig { ... }
interface NostrEvent { ... }
// Declare classes at top level
declare class NostrAuthClient { ... }
// Global augmentations after type definitions
declare global {
interface Window {
NostrAuthMiddleware: typeof NostrAuthClient;
}
}
// Single export at the end
export = NostrAuthClient;
This pattern ensures better IDE support and cleaner type declarations. For more details, see our TypeScript Guide.
For client-side applications, we provide a lightweight browser-based authentication flow using NIP-07. This implementation works directly with Nostr browser extensions like nos2x or Alby.
// Browser
const auth = new NostrAuthMiddleware.NostrBrowserAuth({
customKind: 22242, // Optional: custom event kind
timeout: 30000 // Optional: timeout in milliseconds
});
// Get user's public key
const publicKey = await auth.getPublicKey();
// Sign a challenge
const challenge = await auth.signChallenge();
// Verify a session
const isValid = await auth.validateSession(session);
For applications that authenticate users via NIP-46 bunkers (remote signers like NostrKey), instead of directly through browser extensions.
import { Nip46AuthHandler } from 'nostr-auth-middleware/browser';
const auth = new Nip46AuthHandler({
bunkerUri: 'bunker://<remote-pubkey>?relay=wss://relay.example.com&secret=...',
serverUrl: 'https://auth.example.com',
});
// Provide your own relay transport
auth.setTransport({
sendEvent: async (event) => { /* publish to relay */ },
subscribe: (filter, onEvent) => { /* subscribe and call onEvent */ return () => {}; },
});
await auth.connect();
const result = await auth.authenticate();
// result: { pubkey, signedEvent, sessionInfo, timestamp }
import { Nip46SignerMiddleware, createNip46Signer } from 'nostr-auth-middleware';
const signer = createNip46Signer(
{
signerSecretKey: process.env.SIGNER_SECRET_KEY,
relays: ['wss://relay.example.com'],
secret: 'optional-connection-secret',
},
{
getPublicKey: () => myPubkey,
signEvent: (eventJson) => JSON.stringify(signMyEvent(JSON.parse(eventJson))),
}
);
app.use('/nip46', signer.getRouter());
// Routes: POST /nip46/request, GET /nip46/info, GE