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

@cueplusplus/id-nextjs

v0.5.0

Published

CUE++ ID — Next.js integration (cookie adapter, auth handlers, middleware refresh).

Readme

@cueplusplus/id-nextjs

Next.js integration for CUE++ ID: route handlers for the whole OAuth 2.1 / OIDC flow, a sealed httpOnly session cookie, single-flight middleware refresh, and typed server-side session access. Shaped after @supabase/ssr — the . entry is isomorphic, server-only APIs live behind ./server.

Install

pnpm add @cueplusplus/id-nextjs jose

Peer dependencies: next ^16, react ^19, jose ^5.9 || ^6 (the crypto library all token verification and cookie sealing runs through; pnpm ≥8 and npm ≥7 install it for you, Yarn PnP and auto-install-peers=false do not). Configure via env (CUE_ID_ISSUER, CUE_ID_CLIENT_ID, CUE_ID_CLIENT_SECRET, CUE_ID_REDIRECT_URI, CUE_ID_COOKIE_SECRET, optional CUE_ID_SCOPES, CUE_ID_TIMEOUT_MS, CUE_ID_TOKEN_AUTH_METHOD) — it is read lazily, so next build needs no secrets and createServerClient can be constructed at module scope.

| Optional var | Default | Why you would change it | | --- | --- | --- | | CUE_ID_SCOPES | openid profile email | Spec §7.6 allows no others today. | | CUE_ID_TIMEOUT_MS | 5000 | Total budget for one SDK operation; its (at most two) sequential network legs get half each. A rotation runs two operations, so its worst case is 1.5x — values from 6667 up are refused at boot, because that reaches GoTrue's 10s refresh-token reuse interval. | | CUE_ID_TOKEN_AUTH_METHOD | client_secret_basic | Set to client_secret_post if the client is registered with that method (spec §7.3 registers Basic by default for confidential clients). | | CUE_ID_DIAGNOSTIC_HEADERS | (off) | 1 to emit the optional diagnostic headers below. Behaviour is identical either way. |

CUE_ID_COOKIE_SECRET is 32 random bytes, in either base64 alphabet:

openssl rand -base64 32                                                    # standard
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"  # url-safe

Usage

// app/api/auth/[...cueid]/route.ts — sign-in, callback and sign-out
import { createAuthHandlers } from '@cueplusplus/id-nextjs/server'
export const { GET, POST } = createAuthHandlers()
// middleware.ts — refresh sessions that are near expiry
import { updateSession } from '@cueplusplus/id-nextjs/server'
import type { NextRequest } from 'next/server'

export default function proxy(req: NextRequest) {
  return updateSession(req)
}

The middleware is what keeps sessions alive — it is not optional, and its matcher must cover every authenticated route. updateSession is the only place that can refresh: a server component cannot set cookies, so it cannot renew anything. On the server read path (auth(), requireAuth(), getSession(), getAccessToken(), GET /session) an expired session is no session — those helpers return null (and requireAuth throws) once expiresAt has passed, plus a 60s clock allowance. That number is not chosen here: expiresAt is the access token's own verified exp, so the allowance is the same tolerance the jose verifier already applies, and the refresh fires a further 60s ahead of it (120s total) so the window is never reached under middleware coverage. A route the matcher misses therefore signs the user out roughly an hour after sign-in instead of authorizing them on a stale cookie.

// app/profile/page.tsx — server component
import { auth } from '@cueplusplus/id-nextjs/server'

export default async function Profile() {
  const session = await auth()          // AuthContext | null
  if (!session) return <p>Not signed in.</p>
  return <p>{session.user.email} — {session.user.role}</p>
}
// lib/client.ts — client components
'use client'
import { createBrowserClient } from '@cueplusplus/id-nextjs'
export const cue = createBrowserClient()   // pinned to /api/auth

Signing out

POST {baseURL}/sign-out clears the session cookie and attempts to revoke the refresh token upstream, then answers in whichever idiom the caller used:

| Caller | Request | Response | | --- | --- | --- | | HTML form post / navigation | POST with an HTML Accept (or none) | 302 to redirectTo (default /) | | Programmatic client (@cueplusplus/id-react) | POST with Accept: application/json | 204, no redirect |

The 204 exists because fetch follows redirects by default, which would make res.ok report the status of the page you were redirected TO — a gated or cross-origin / would look like a failed sign-out after the cookie was already cleared. @cueplusplus/id-react also sends redirect: 'manual', so both answers are read correctly.

createServerClient(cookieStore) binds the SDK to any getAll/setAll cookie store — the same adapter contract as @supabase/ssr, so both SDKs can share one Next.js cookies() jar. auth() and getSession() are thin wrappers over it bound to Next's own jar.

Roles

requireAuth({ role }) is a hierarchy, not an exact match: admin ⊇ member ⊇ guest, so an admin satisfies a member-gated route.

user.role is derived from the user_role claim on the verified access token (the Custom Access Token Hook is what injects it) and is re-derived on every refresh, so a role change propagates within one refresh cycle. If the claim is missing the role is guest — never a cached or invented value — and the middleware marks that response x-cue-id-role-claim: absent so a deployment whose hook is not firing is visible in monitoring rather than silently demoting everyone.

The diagnostic headers below are opt-in: set CUE_ID_DIAGNOSTIC_HEADERS=1 to emit them. They are telemetry, not mechanism — every condition they describe drives the same behaviour whether or not the headers are on — so leaving them off costs diagnosis speed, never correctness. x-cue-id itself is always emitted; spec §9.3 mandates that marker.

x-cue-id-role-claim distinguishes three different incidents — they have different causes and different fixes, so they do not share a flag:

| Value | Meaning | What to do | | --- | --- | --- | | (absent) | The token verified and carried user_role. | Nothing. | | absent | The token verified; there was no user_role claim. | The Custom Access Token Hook is not firing for this deployment. | | unverified | The access token did not verify. | The signing key or the aud pin is wrong. Role falls back to guest. | | stale | Verification could not be performed (e.g. JWKS unreachable). | The previous role is carried for at most one refresh cycle, and only from a verification less than an hour old. A repeated fault stops carrying and falls back to guest — see x-cue-id-warn: role-carry-expired. |

x-cue-id-warn carries non-role diagnostics as a comma-separated list: cookie-overflow (the rotated session exceeded the browser's cookie limit and the session was cleared rather than silently dropped), claims-unverified (an id_token arrived on a refresh and failed verification; the session was kept with its previous claims), role-carry-expired (a transient verification fault recurred, so the previous role was not carried again and the user is now guest), and subject-mismatch (a rotation returned a token for a different subject; the session was cleared).

POST /sign-out clears the session cookie and sets x-cue-id-signout: local (gated, like every other diagnostic).

Sign-out is local, and that is a provider fact rather than a shortcut. Supabase's OAuth server has no token-revocation endpoint: {issuer}/oauth/revoke returns 404 with a body byte-identical to a path invented on the spot, and discovery advertises no revocation_endpoint (probed 2026-08-19). This handler used to POST there and report x-cue-id-revoke: ok | failed | skipped; since the call could only ever fail, the header's failure value was the steady state — an alarm that is always on. Both the call and the header are gone.

What that means for your users: the cookie is cleared, so they are signed out of your app. The refresh token stays valid at the provider until it expires. Ending the CUE++ ID session itself is a deliberate action on the account page at id.cueplusplus.com (GoTrue's POST /auth/v1/logout, an endpoint that does exist). Ending every session for a user is admin.users.revokeSessions in @cueplusplus/id-admin. Grant revocation — DELETE {issuer}/user/oauth/grants?client_id=…, authenticated as the user and forcing re-consent — is a different operation and is not wired up.

x-cue-id-signout is gated because there is nothing to hide: clearing a cookie in a response the caller receives cannot half-happen. The unconditional-emit carve-out that x-cue-id-revoke: failed had existed because a failed revoke had no other outlet; with no revoke, there is no such signal left.

Limits and failure modes

  • The sealed cookie must stay under the browser's 4096-byte limit. Sign-in fails with an explicit error above it rather than letting the browser drop the cookie silently (which presents as an endless sign-in loop). Large user_metadata or custom claims are what push it over.
  • Every SDK operation has a total deadline — CUE_ID_TIMEOUT_MS, default 5000ms — and its at most two sequential legs get half each, so a stalled token endpoint degrades instead of holding the request open.
  • The session cookie's Max-Age and the seal's own expiry are both 14 days.

Entry points

| Subpath | Exports | | ---------- | ------- | | . | createBrowserClient; also createAuthHandlers and updateSession, kept for compatibility — import those from ./server | | ./server | createAuthHandlers, updateSession, auth, requireAuth, getSession, getAccessToken, buildSignInUrl, createServerClient (server-only) |

Import createAuthHandlers and updateSession from ./server. The . entry also exports createBrowserClient, whose module imports @cueplusplus/id-react, which calls React's createContext as it loads. React's server build has none, so a route handler or a proxy that imports from . cannot load at all. | ./react | the full @cueplusplus/id-react surface |

A runnable consumer lives in apps/example.

License

MIT — see LICENSE.