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

@iaurelius/accounts-nextjs

v0.4.0

Published

Accounts by iAurelius for Next.js (App Router): sign-in on the hosted page, sessions in an encrypted cookie, auth() on the server and <UserButton /> in your UI.

Readme

@iaurelius/accounts-nextjs

Accounts by iAurelius for Next.js (App Router, Next 15.5.24+ and 16.3.3+).

People sign in on your application's hosted page, on your domain with passkeys, email codes and Google, and come back signed in. In your app you get:

  • auth() and currentUser() on the server;
  • useAuth() and useUser() in the browser;
  • ready-made <SignInButton />, <UserButton />, <SignedIn> and <SignedOut>.

The tokens stay on the server, in an encrypted HttpOnly cookie. The SDK refreshes them before they expire.

Quick start

npx @iaurelius/accounts init

It installs the package, registers your development app, and writes .env.local and proxy.ts. To do it by hand, follow the three steps below.

1. Settings (.env.local)

Copy them from your application in the console, under Apps and APIs:

NEXT_PUBLIC_ACCOUNTS_PUBLISHABLE_KEY=pk_live_…
ACCOUNTS_CLIENT_ID=…
# Production (a web app, https): its secret. The development app (localhost) has none.
ACCOUNTS_CLIENT_SECRET=…
# Always: your own random secret for the session cookie (openssl rand -base64 32), one per environment.
ACCOUNTS_COOKIE_SECRET=…
# Optional: your API's identifier, for getToken().
# ACCOUNTS_AUDIENCE=https://api.example.com

ACCOUNTS_COOKIE_SECRET is required, in development and in production. It is the key of the session cookie, so whoever has it can write a session for anyone: keep it on your app's servers only, and never reuse the client secret for it (the SDK refuses to start if you do). Changing it signs everyone out.

The development app is a public app, with no secret and PKCE only. It allows http://localhost redirects, which a web app cannot have. Register it with this callback, and use your real port:

  • http://localhost:3000/api/accounts/callback as the redirect URI;
  • http://localhost:3000/ as the sign-out URI.

In production, a web app (confidential, https) uses the same two URLs on your domain.

2. proxy.ts

Use middleware.ts before Next 16.

import { accountsMiddleware } from "@iaurelius/accounts-nextjs/server";

export default accountsMiddleware({ protect: ["/dashboard(.*)"] });

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

The proxy does four things:

  • It answers /api/accounts/{sign-in,sign-up,callback,sign-out,session,token}, so you write no route files.
  • It keeps the session fresh.
  • It sends people who are signed out of a protected page to the sign-in, and back to that page afterwards. Protected API routes answer 401 instead.
  • It hands the session to the rest of the request.

Keep the matcher as it is: it runs the proxy on every path except Next's build output (/_next/static/… and /_next/image) and /favicon.ico itself, each matched exactly. A matcher that skips paths by their extension (.png, .csv…) also skips your own pages asked that way, such as app/files/[name]/page.tsx for /files/a.png; one that skips a prefix without anchoring it (favicon.ico instead of favicon\.ico$) skips /favicon.ico-acme/dashboard for app/[team]/dashboard.

protect is a redirect for people, not the lock on your data. Check who is asking where the data is read: await auth.protect() in the page or layout, and auth() in route handlers and server actions. That check reads the session itself, so it holds even for a request the proxy did not see.

3. app/layout.tsx

import { AccountsProvider, SignInButton, SignedIn, SignedOut, UserButton } from "@iaurelius/accounts-nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AccountsProvider>
          <header>
            <SignedOut><SignInButton /></SignedOut>
            <SignedIn><UserButton /></SignedIn>
          </header>
          {children}
        </AccountsProvider>
      </body>
    </html>
  );
}

<UserButton /> is the avatar menu. It shows who is signed in and has "Manage account" and "Sign out":

  • "Manage account" opens the hosted account page, which has a way back to where the person was. For that way back, allow your app's origin in the console under Emails and paths → Allowed origins.
  • "Sign out" also ends the session on the hosted pages.

The buttons speak English or Spanish: <AccountsProvider locale="es">.

On the server

import { auth, currentUser } from "@iaurelius/accounts-nextjs/server";

export default async function Page() {
  const { userId, user } = await auth.protect(); // signed out: to the sign-in and back
  const me = await currentUser(); // the latest profile, from the application
  return <p>Hello {user.name ?? user.email}</p>;
}

auth() returns:

  • isAuthenticated, userId, sessionId, orgId and user;
  • getToken(), the access token for your API;
  • protect() and redirectToSignIn().

It works in server components, route handlers and server actions.

In the browser

"use client";
import { useAuth, useUser } from "@iaurelius/accounts-nextjs";

const { isSignedIn, getToken, signOut } = useAuth();
const { user } = useUser();
const res = await fetch("https://api.example.com/me", { headers: { authorization: `Bearer ${await getToken()}` } });

Your API checks that token with @iaurelius/accounts, for example with accountsMiddleware() for Hono.

Security

  • Sign-in:
    • Authorization code with PKCE (S256), state and nonce, and RFC 9207's iss checked.
    • The ID token is verified: signature, audience and nonce.
  • Session cookie: encrypted (A256GCM) with a key derived from ACCOUNTS_COOKIE_SECRET (never from the client secret), HttpOnly and SameSite=Lax. On https it is __Host-accounts_session, Secure and host-only, so another subdomain of your domain cannot plant or shadow it; the sign-in attempt's cookie is __Host- too. The plain __accounts_session of earlier versions is not read on https and is removed when it comes along. auth() on a request the proxy did not see reads the plain name only when the app is on http for sure (ACCOUNTS_APP_URL, or else the request's x-forwarded-proto, says http). Responses that set it are Cache-Control: private, no-store.
  • Redirects: after sign-in the person only ever goes back to a path of your app.
  • Sign-out: only from your app's own pages (Origin checked). The refresh token is revoked and the hosted session ends.
  • Revocation: a person who is banned or signed out everywhere is out at the next refresh.
  • Refresh: only a refusal for good signs the person out: a 4xx with an OAuth error, such as invalid_grant (revoked, banned), invalid_client, invalid_scope or invalid_target (your audience changed). If the application answers 429 or 5xx, does not answer, or a 4xx comes without an OAuth error (a firewall's page), the session and its refresh token are kept and the access token serves while it lasts. After a 429 (or a 503 with Retry-After), no refresh of the app is tried before the server's Retry-After; after anything else, only that person's refresh waits a few seconds. A refreshed ID token must be the same person (iss, sub, aud), and userinfo's sub must match.

License

MIT