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

@lumoauth/nextjs

v1.0.0

Published

LumoAuth for Next.js — server-side auth() for App Router, route protection middleware, and a backend-for-frontend that keeps tokens out of the browser.

Readme

@lumoauth/nextjs

LumoAuth for Next.js: server-side auth() in the App Router, route-protection middleware, and a backend-for-frontend that keeps tokens out of the browser.

Why this exists

A purely client-side auth provider cannot render a signed-in page on the server. It has to boot, read storage, and fetch the profile — so the first paint is always signed-out and corrects itself after hydration. That is the flash.

This package puts the session in an httpOnly cookie the server can read, so a page knows who the user is before it responds.

Setup

npm install @lumoauth/nextjs
# .env.local
NEXT_PUBLIC_LUMOAUTH_DOMAIN=https://app.lumoauth.dev
NEXT_PUBLIC_LUMOAUTH_ORG_ID=acme-corp
NEXT_PUBLIC_LUMOAUTH_CLIENT_ID=your-client-id
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000/api/auth/callback

# Seals the session cookie. Never expose this: openssl rand -hex 32
LUMOAUTH_SECRET=...

Mount the backend-for-frontend once:

// app/api/auth/[...lumoauth]/route.ts
import { createRouteHandler } from '@lumoauth/nextjs/server';
export const { GET, POST } = createRouteHandler();

That gives you /api/auth/login, /callback, /session and /logout. Register the callback URL as a redirect URI on your OAuth client, and the origin as a post-logout redirect URI so sign-out returns to your app.

Server components

import { auth, currentUser } from '@lumoauth/nextjs/server';

export default async function Page() {
  const { isSignedIn, userId } = await auth();
  if (!isSignedIn) return <a href="/api/auth/login">Sign in</a>;

  const user = await currentUser();
  return <p>Hello {user?.email}</p>;
}

auth() is cheap — it reads and decrypts a cookie. currentUser() makes a network call to /userinfo, so prefer auth() when the subject is enough.

To require a session, await protectPage() redirects signed-out visitors.

Middleware

// middleware.ts
import { lumoAuthMiddleware } from '@lumoauth/nextjs/server';

export default lumoAuthMiddleware({ protect: ['/dashboard/:path*'] });
export const config = { matcher: ['/((?!_next|.*\\..*).*)'] };

Convenience, not the security boundary — that is your API rejecting a bad token. It only checks that a valid, unexpired session cookie is present.

Client components

'use client';
import { LumoAuthNextProvider, useUser } from '@lumoauth/nextjs';

<LumoAuthNextProvider> is <LumoAuthProvider> wired to the cookie session, so client and server agree. Every hook and component from @lumoauth/react is re-exported.

How the session is protected

The cookie is encrypted and authenticated with AES-256-GCM using LUMOAUTH_SECRET, via Web Crypto so the same code runs in the Node and edge runtimes. Encrypted rather than merely signed because the payload holds the refresh token, which must never be readable by the browser. A tampered cookie fails the auth tag and is treated as no session at all.

auth() does not re-verify the access token against the issuer's JWKS. It does not need to: the token is only ever in that cookie because this package put it there after a successful exchange, and the cookie is unforgeable.

Sessions vs access tokens

These are two different lifetimes, and treating them as one is the classic way to log everybody out every hour.

| | Lifetime | Meaning | |---|---|---| | Session | weeks (sessionMaxAge) | The user is signed in | | Access token | ~1 hour (issuer's choice) | A credential for calling the API |

auth().isSignedIn reports on the session. A user whose access token expired a minute ago is still signed in; the token just needs replacing.

lumoAuthMiddleware() does that replacing, in the background, before the page renders. Install it and getToken() always returns a fresh token:

export default lumoAuthMiddleware();   // refresh only, protect nothing

Why refresh lives in middleware

Only middleware, route handlers, and server actions can write cookies in Next.js. Server components cannot — so a server component can never persist a refreshed token.

That is not a technicality to route around. LumoAuth rotates refresh tokens and revokes the old one on use, so refreshing somewhere that cannot save the result would spend the token and break the session on the very next request. auth() therefore returns getToken() === null and isStale === true rather than performing a refresh it cannot persist.

Middleware writes the new cookie on the response, but the page reads the request — so the fresh session is also forwarded on a request header that auth() prefers. Without that, a refresh would not take effect until the following request.

This is the same problem Clerk solves with its handshake, but simpler here: Clerk must bounce the browser through its API because only that origin holds the session, whereas this package holds the refresh token in its own cookie and can refresh server-to-server with no redirect.

Limitations

  • Without lumoAuthMiddleware(), getToken() returns null once the access token goes stale (isStale tells you why). The session stays valid; nothing is silently broken, but you get no token until something refreshes it.
  • Pages Router is not supported yet. App Router only.