How LunaTV Manages Authentication: Cookie-Based Security with HMAC Signatures

LunaTV implements a dual-mode cookie authentication system that either stores a raw password for local-storage deployments or generates HMAC-SHA-256 signed tokens for database-backed environments, with all requests validated through Next.js middleware.

The LunaTV project (MoonTechLab/LunaTV) provides a flexible authentication layer designed to adapt across multiple storage backends including local-storage, Redis, Upstash, and Kvrocks. The implementation balances simplicity for single-user instances with cryptographic security for multi-user productions, utilizing signed cookie tokens and environment-based secrets.

The authentication process begins at the /api/login endpoint defined in src/app/api/login/route.ts. This route handles credential verification differently depending on the configured storage mode.

Local-Storage Mode

In local-storage mode, the system performs a direct password comparison against the PASSWORD environment variable. Upon successful validation, the generateAuthCookie function creates a cookie containing the raw password and user role.

// POST /api/login (local-storage mode)
const { password } = await req.json();
if (password === process.env.PASSWORD) {
  const cookie = await generateAuthCookie(undefined, password, 'user', true);
  const resp = NextResponse.json({ ok: true });
  resp.cookies.set('auth', cookie, { path: '/', expires: /* 7 days */ });
  return resp;
}

Database Mode with Cryptographic Signing

For Redis, Upstash, or Kvrocks backends, credentials are verified via db.verifyUser. Successful authentication generates a cookie that excludes the password but includes the username, a cryptographic HMAC-SHA-256 signature of the username (using PASSWORD as the secret), a timestamp, and the user's role. The payload is URL-encoded JSON.

const { username, password } = await req.json();
const ok = await db.verifyUser(username, password);
if (ok) {
  const cookie = await generateAuthCookie(username, undefined, user.role);
  const resp = NextResponse.json({ ok: true });
  resp.cookies.set('auth', cookie, { path: '/', expires: /* 7 days */ });
  return resp;
}

The src/lib/auth.ts file provides the core utilities for parsing authentication state. The getAuthInfoFromCookie function reads the auth cookie server-side, decodes the URL-encoded value, and parses the JSON payload. For client-side usage, getAuthInfoFromBrowserCookie performs identical operations using document.cookie.

Middleware Enforcement and Signature Verification

The global middleware in src/middleware.ts intercepts all protected routes to enforce authentication policies. It first checks shouldSkipAuth to bypass static or public paths, then validates the cookie payload.

Validation Logic

In local-storage mode, the middleware simply verifies that the stored password matches process.env.PASSWORD. For database modes, it requires both a username and signature field.

const authInfo = getAuthInfoFromCookie(request);
if (!authInfo?.username || !authInfo?.signature) return handleAuthFailure(...);
const isValid = await verifySignature(
  authInfo.username,
  authInfo.signature,
  process.env.PASSWORD!
);
if (!isValid) return handleAuthFailure(...);

The verifySignature function recreates the HMAC key from the PASSWORD environment variable and validates the supplied signature against the username. Invalid or missing credentials result in 401 responses or redirects to the login page.

Security Architecture

The LunaTV authentication system implements several protective measures:

  • Password Isolation: In non-local-storage modes, passwords never persist in cookies.
  • Cryptographic Integrity: HMAC-SHA-256 signatures prevent tampering with the username field.
  • Replay Protection: The auth.timestamp field mitigates replay attacks by allowing token expiration checks.
  • Role-Based Access: Cookies include a role field parsed from src/lib/config.ts for authorization decisions.

Summary

  • LunaTV supports both simple password storage and cryptographically signed tokens via src/app/api/login/route.ts.
  • The generateAuthCookie function creates HMAC-SHA-256 signed payloads for database modes, while local-storage mode stores raw passwords.
  • src/middleware.ts enforces authentication globally, verifying signatures using verifySignature and the PASSWORD environment secret.
  • Cookie parsing utilities in src/lib/auth.ts handle both server-side request objects and client-side document.cookie.

Frequently Asked Questions

How does LunaTV store passwords in authentication cookies?

In local-storage mode, LunaTV stores the raw password in the cookie. For database-backed modes (Redis, Upstash, Kvrocks), the cookie stores only the username, role, timestamp, and an HMAC-SHA-256 signature of the username—never the password itself.

What prevents attackers from forging authentication cookies?

The system uses HMAC-SHA-256 signatures generated with the PASSWORD environment variable as the secret key. The verifySignature function in the middleware recalculates the expected signature for the provided username; any tampering with the cookie payload invalidates the signature, causing immediate rejection.

How does LunaTV handle authentication across different storage backends?

The src/lib/db.ts abstraction layer provides db.verifyUser for credential validation against Redis, Upstash, or Kvrocks. The src/lib/config.ts file loads user configurations including roles and ban status. Local-storage mode bypasses the database entirely, comparing credentials directly against the PASSWORD environment variable.

Can the authentication tokens be replayed by attackers?

Each signed cookie includes a timestamp field that enables replay attack mitigation. While the source code stores this timestamp, implementations can validate token age against current server time to reject expired or reused tokens from previous sessions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →