npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

login-with-lightning

v1.0.1

Published

Drop-in Lightning Login widget — add passwordless LNURL-auth to any website in minutes.

Readme

Login with Lightning ⚡

Prove you control this key.

You can't access an account without authentication. login-with-lightning is the gate — drop-in LNURL-auth for any website. Users scan a QR code with their Lightning wallet and they're in. No emails, no passwords, no third-party OAuth. Just cryptographic key pairs and the open Lightning protocol.

Part of the constraint chain: identity (login-with-lightning) enables trust (ai-wot) enables payment (lightning-agent).

Installation

npm install login-with-lightning

Peer dependencies: express (v4+)

Quick Start

Server (Express)

const express = require('express');
const { lightningAuth } = require('login-with-lightning/server');

const app = express();

const auth = lightningAuth({
  callbackUrl: 'https://yoursite.com/auth/lightning/verify',
  jwtSecret: 'your-secret-key-change-this'
});

app.use('/auth', auth);

// Protected route
app.get('/api/me', auth.requireAuth, (req, res) => {
  res.json({ pubkey: req.user.pubkey });
});

app.listen(3000);

Client (Script Tag)

<script src="/path/to/widget.js"></script>

<div id="login"></div>

<script>
  LightningLoginWidget.create('#login', {
    endpoint: '/auth/lightning',
    onSuccess: (token, pubkey) => {
      console.log('Authenticated!', pubkey);
    }
  });
</script>

Client (ES Module / Bundler)

const { LightningLoginWidget } = require('login-with-lightning/client');

const widget = new LightningLoginWidget({
  endpoint: '/auth/lightning',
  onSuccess: (token, pubkey) => {
    console.log('Authenticated!', pubkey);
  }
});

widget.mount('#login');

React

const { LightningLogin, useLightningAuth } = require('login-with-lightning/client/react');

function App() {
  const { token, pubkey, logout } = useLightningAuth();

  return (
    <div>
      <LightningLogin
        endpoint="/auth/lightning"
        theme="dark"
        onSuccess={(token, pubkey) => console.log('Logged in!', pubkey)}
      />
      {pubkey && <p>Logged in as {pubkey}</p>}
      {token && <button onClick={logout}>Logout</button>}
    </div>
  );
}

Demo

Run the included demo to see it in action:

cd demo
npm install
node server.js
# Open http://localhost:3000

API Reference

Server

lightningAuth(options)

Creates Express router middleware with LNURL-auth routes.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | callbackUrl | string | required | Public URL wallets will call back to (must be /auth/lightning/verify) | | jwtSecret | string | required | Secret for signing JWT tokens | | jwtExpiresIn | string | '24h' | JWT token lifetime (e.g. '1h', '7d') | | challengeTtlMs | number | 300000 | Challenge validity in milliseconds (default 5 min) | | onAuth | function | null | Callback (pubkey, token) fired on successful authentication |

Routes created:

| Route | Description | |-------|-------------| | GET /lightning | Generate challenge — returns { k1, lnurl, expiresAt, qr } | | GET /lightning/verify | Wallet callback — verifies signature, returns { status: "OK" } | | GET /lightning/status/:k1 | Frontend polling — returns { status, token?, pubkey? } |

Utilities on the router:

  • router.requireAuth — Express middleware that checks Authorization: Bearer <token> header. Sets req.user with { pubkey, iat, exp }.
  • router.verifyToken(token) — Manually verify a JWT. Returns decoded payload or null.

Client

new LightningLoginWidget(options)

Creates a login widget instance.

Options:

| Option | Type | Default | Description | |--------|------|---------|-------------| | endpoint | string | '/auth/lightning' | Server auth endpoint path | | theme | string | 'dark' | 'dark' or 'light' | | buttonText | string | 'Login with Lightning ⚡' | Button label | | title | string | 'Login with Lightning ⚡' | Modal title | | subtitle | string | 'Scan with your Lightning wallet' | Modal subtitle | | pollInterval | number | 2000 | Status polling interval in ms | | storageKey | string | 'lwl_token' | localStorage key for persisting JWT | | storeToken | boolean | true | Whether to store the JWT in localStorage | | onSuccess | function | null | Callback (token, pubkey) on successful auth | | onError | function | null | Callback (error) on failure | | onCancel | function | null | Callback when user closes modal | | accentColor | string | null | Custom accent color (CSS color value) | | css | string | null | Custom CSS to inject instead of defaults |

Methods:

| Method | Description | |--------|-------------| | mount(target) | Mount the button into a DOM element (selector string or element) | | unmount() | Remove the widget and clean up | | open() | Programmatically open the login modal | | closeModal() | Close the modal | | getToken() | Get the stored JWT token | | logout() | Remove stored token and re-render button |

Static:

  • LightningLoginWidget.create(selector, options) — Create and mount in one call.

React Component

<LightningLogin />

React component wrapping the vanilla widget. Accepts all widget options as props.

useLightningAuth(storageKey?)

React hook that returns { token, pubkey, logout }.

How LNURL-auth Works

LNURL-auth (LUD-04) is a passwordless authentication protocol:

  1. Server generates a challenge — a random 32-byte k1 value, encoded as an LNURL (bech32-encoded URL)
  2. User scans QR code — their Lightning wallet reads the LNURL and extracts the challenge
  3. Wallet signs the challenge — using secp256k1 (the same cryptography as Bitcoin), the wallet signs k1 with the user's private key
  4. Wallet sends signature backGET callback?k1=...&sig=...&key=...
  5. Server verifies — checks the signature against the public key. If valid, the user is authenticated
  6. Session established — server issues a JWT containing the user's public key

The user's identity is their public key. No personal information is exchanged. Different services see different derived keys (per the LNURL-auth spec), preserving privacy.

Security Considerations

  • JWT Secret: Use a strong, random secret in production. Never use the demo default.
  • HTTPS Required: The callbackUrl must be HTTPS in production. Lightning wallets won't call back to HTTP URLs (except localhost for development).
  • Challenge Expiry: Challenges expire after 5 minutes by default. Adjust challengeTtlMs as needed.
  • One-time Use: Each challenge can only be used once. Replaying a signature will fail.
  • Token Storage: JWTs are stored in localStorage by default. For higher security, set storeToken: false and handle storage yourself (e.g., httpOnly cookies via your server).
  • No Password Equivalent: Unlike passwords, LNURL-auth keys can't be phished — each domain gets a unique derived key, and the signing happens entirely in the user's wallet.

Compatible Wallets

Any wallet supporting LNURL-auth (LUD-04), including:

  • Phoenix
  • Zeus
  • Breez
  • BlueWallet
  • Alby (browser extension)
  • Blixt
  • And many more

Package Structure

login-with-lightning/
  src/
    server/
      middleware.js    — Express middleware
      jwt.js          — JWT helpers
      index.js        — Server exports
    client/
      widget.js       — Vanilla JS widget (includes built-in QR generator)
      widget.css      — Standalone CSS file
      react.jsx       — React wrapper component
      index.js        — Client exports
    index.js          — Package entry point
  demo/
    server.js         — Demo Express server
    public/
      index.html      — Demo page
  LICENSE             — MIT
  README.md

License

MIT