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

@marlinjai/auth-brain-nextjs

v0.8.0

Published

Next.js integration for the Lumitra auth-brain identity service: middleware, session helpers, and fail-closed permission guards

Readme

@marlinjai/auth-brain-nextjs

Next.js integration for the Lumitra auth-brain identity service: middleware, server-component session helpers, and fail-closed permission guards. Extracted from the battle-tested lumitra-studio integration; the semantics (Authorization-header precedence, fail-closed can(), dual-accept SERVICE_TOKEN rotation, dev bypass) are preserved exactly.

Setup

// src/lib/auth.ts — the single config entry point
import { createAuthBrainNextjs } from '@marlinjai/auth-brain-nextjs';

export const auth = createAuthBrainNextjs({
  appName: 'receipts',
  // Multi-workspace mode: every session workspace whose slug starts with the
  // prefix is a membership; the active one is a validated cookie selection.
  workspaces: { slugPrefix: 'receipts-' },
  activeWorkspaceCookie: 'receipts_ws',
  // Single-workspace mode instead: { slug: 'lumitra-studio', slugEnvVar: 'STUDIO_WORKSPACE_SLUG' }
  permissions: {
    'receipts.upload': 'workspace.member',
    'receipts.row.write': 'workspace.member',
  },
  publicPaths: ['/api/health'],
  publicUrl: 'https://receipts.lumitra.co',
});
// src/middleware.ts
import { auth } from '@/lib/auth';

export const runtime = 'nodejs'; // timingSafeEqual is not available on Edge

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|robots.txt).*)'],
};

export default auth.createAuthMiddleware();

Pages: const session = await auth.requireSession('/dashboard'). Mutating routes/actions: const { denied, auth: principal } = await auth.guardMutation(req, 'receipts.upload'); if (denied) return denied; (in server actions, which have no NextRequest, use await auth.requireAction('receipts.upload', resourceWorkspaceId) — it throws AuthBrainActionError on deny). Workspace switcher: wrap auth.setActiveWorkspace(id) in a 'use server' action.

Load-bearing requirements in the consuming middleware file (the package cannot enforce these for you):

  • export const runtime = 'nodejs' — the constant-time token compare uses node:crypto timingSafeEqual, which does not exist on the Edge runtime. Omitting this silently breaks the service-token gate.
  • The config.matcher must cover every page and /api/* route except Next internals/static assets. A route outside the matcher is UNGUARDED.

Multi-workspace (slugPrefix) consumers MUST scope mutations to the resource's true owner. guardMutation/requireAction default the can() target to the session's ACTIVE workspace when resourceWorkspaceId is omitted — correct only when the resource genuinely belongs to the active workspace. For anything addressed by an opaque id (a row, a table), resolve the OWNING workspace server-side and pass it as resourceWorkspaceId, and check it against session.memberships for reads. Otherwise a member of several workspaces can touch workspace B's data while the check runs against workspace A.

Behavior contract

  • An Authorization header always means a machine caller: bearer compare (constant-time, dual-accept SERVICE_TOKEN/SERVICE_TOKEN_NEXT), never a fallthrough to the session cookie.
  • No header: the lumitra_session cookie is verified against auth-brain (30s cache); workspace membership (matched by slug/prefix) IS the gate, re-checked every request.
  • A verified session with zero matched workspaces gets /no-access (pages) or JSON 401 (/api/*) — never a login loop.
  • The active-workspace cookie is a selector into the verified membership set, never a credential: invalid/absent falls back to the first membership (sorted by slug).
  • can() (OpenFGA) errors and false are equally a DENY (fail-closed). An un-provisioned OpenFGA never silently grants.
  • All env is read lazily; next build needs no runtime secrets. Credentials are never logged.

Env

AUTH_BRAIN_URL (default https://auth.lumitra.co), OPENFGA_API_URL, OPENFGA_STORE_ID, OPENFGA_AUTHORIZATION_MODEL_ID, OPENFGA_API_TOKEN, SERVICE_TOKEN (+ SERVICE_TOKEN_NEXT during rotation), AUTH_DEV_USER_EMAIL (development only — bypasses auth for local UI work).

oidc mode (apps on their own domain)

The shared-cookie setup above needs the app on *.lumitra.co. For any other domain, configure oidc mode: the app logs people in through auth-brain's OpenID Connect authorization-code flow and keeps its own session cookie on its own hostname.

// src/lib/auth.ts
export const auth = createAuthBrainNextjs({
  appName: 'opuntia-studio',
  mode: { oidc: { requireAmr: ['mfa'] } },          // all other fields have defaults
  workspaces: { appGrant: { app: 'opuntia-studio' } },
  permissions: { 'opuntia-studio.admin.access': 'workspace.member' },
  publicPaths: ['/api/health'],
  publicUrl: 'https://opuntiagatherings.com',
});

// src/app/api/auth/[action]/route.ts
import { auth } from '@/lib/auth';
const handlers = auth.oidcHandlers();
export const runtime = 'nodejs';
export async function GET(req: NextRequest, { params }: { params: { action: string } }) {
  const h = handlers[params.action as keyof typeof handlers];
  return h ? h(req) : new Response(null, { status: 404 });
}

Register the client on auth-brain (/admin/oauth-clients, or the machine API) with redirect_uri exactly https://<app>/api/auth/callback, and set OIDC_CLIENT_ID, OIDC_CLIENT_SECRET and AUTH_SESSION_SECRET (32+ chars) in the app's environment. The middleware, getSession, requireSession, guardMutation and requireAction behave exactly as in cookie mode; an unauthenticated page navigation goes to /api/auth/login?return_to=<path>, which starts the flow, and a signed-in person without the app grant lands on /no-access. Node runtime is required, as before.

Branded sign-in landing and silent sign-in

Set signInPath (e.g. '/admin/sign-in') to a public page that renders the SignInWithLumitra button (@marlinjai/auth-brain-nextjs/signin) with href={auth.loginUrl(returnTo)}. An unauthenticated page load then first tries a silent sign-in (/api/auth/login?prompt=none): someone already signed in at auth.lumitra.co gets straight in with zero clicks, and only a cold visitor is sent to the landing (the provider answers login_required, the callback redirects to signInPath?return_to=<path>).

  • Signing out of the app sets a <appName>_oidc_nosilent marker cookie, so a still-warm provider session cannot silently sign the person back in. A failed silent attempt sets it for five minutes, so a cold visitor is not bounced through the provider on every page. The next successful sign-in clears it.
  • Only full page loads try silently; Next.js prefetches and client-side router fetches go straight to the landing.
  • silentSignIn: false turns the silent attempt off and always shows the landing.

Popup sign-in

<SignInWithLumitra href={auth.loginUrl(returnTo)} mode="popup" /> runs the sign-in in a separate window, the way "Sign in with Google" does. The app's callback answers that window with a small page that reports the destination back to the waiting tab (over a same-origin BroadcastChannel, which survives the Google step severing window.opener, with postMessage as a second channel) and closes itself; the tab then navigates with its new session.

  • A blocked popup, a modified click (new tab or window) and no JavaScript all fall back to the plain link.
  • While waiting, the button reads "Continue in the sign-in window" (a second click focuses that window) and a "continue in this tab" link stays available.
  • Errors are shown inside the popup; no entitlement reports the no-access page as the destination.
  • The completion page carries a strict Content-Security-Policy pinned to its one inline script's hash. No provider or client registration change is needed: the popup id never leaves the app.