Skip to content

jeletor/login-with-lightning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors