- Never hardcode private keys in source code
- Use environment variables or secure vaults for key storage
- Rotate keys periodically in production environments
- Use separate key pairs for different environments (dev, staging, production)
- Use a strong, unique secret for JWT signing (minimum 256 bits of entropy)
- Never expose the JWT secret in client-side code
- Rotate secrets periodically in production
- Use different secrets for development and production
# Generate a secure JWT secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"- Client sends their Nostr public key
- Server generates a challenge
- Client signs the challenge with their private key (via NIP-07 extension)
- Server verifies the signature against the public key
- Server issues a JWT token on successful verification
- Use short expiration times (15 minutes to 24 hours)
- Include only necessary claims (
pubkey,iat,exp) - Store tokens securely (httpOnly cookies or secure storage)
- Implement token refresh for long-lived sessions
- Clear tokens on logout
| Method | Security | Use Case |
|---|---|---|
| httpOnly cookie | High | Server-rendered apps |
| sessionStorage | Medium | Single-tab SPAs |
| localStorage | Lower | Persistent sessions |
- The middleware detects browser Nostr extensions (nos2x, Alby) automatically
- Private keys never leave the extension — signing happens in the extension
- Session verification checks extension availability and public key match
The middleware integrates with express-rate-limit for protection against brute-force attacks:
import rateLimit from 'express-rate-limit';
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
});
app.use('/auth', authLimiter);If you discover a security vulnerability, please report it through GitHub's Security Advisory feature. Do not open a public issue.