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

@freebuff/auth

v0.1.0

Published

Sign in with Freebuff: add Freebuff accounts to any site with no secrets. OIDC + PKCE for Cloudflare Workers, Bun, Node and Next.js.

Readme

Sign in with Freebuff

@freebuff/auth lets anyone with a Freebuff account sign in to your site: Google, GitHub or Apple through Freebuff, with nothing to configure and no secrets to keep. Your server learns who the visitor is (sub, and with their consent their email, name and picture). It never sees their password, provider credentials or Freebuff account token.

It is standard OpenID Connect (authorization code + PKCE) with a server-side session, and runs anywhere with fetch and Web Crypto: Cloudflare Workers, Bun, Node 20+, Deno and Next.js.

1. Get a client ID

Each project environment has its own public client ID, registered by the project's creator against exact callback URLs:

# Production: the site's real origin
npx @freebuff/auth register --project notes --name "Notes" \
  --environment production --redirect-uri https://notes.example/auth/freebuff/callback

# Local development: any port on localhost
npx @freebuff/auth register --project notes --name "Notes" \
  --environment development --redirect-uri http://localhost:8787/auth/freebuff/callback

It signs in as you with your Freebuff CLI login (or FREEBUFF_TOKEN) and prints FREEBUFF_CLIENT_ID. That, and your site's origin, are all the configuration there is. Neither is a secret. Re-running it is safe; add a new --redirect-uri when the site gets a new domain.

2. Mount it

Cloudflare Workers, Bun, Deno, Hono

Wrap your fetch handler. It serves the four auth routes, keeps sessions fresh, and makes getUser available anywhere inside:

import { createFreebuffAuth } from '@freebuff/auth'

const auth = createFreebuffAuth({
  clientId: env.FREEBUFF_CLIENT_ID,
  baseUrl: 'https://notes.example', // your canonical origin
})

export default {
  fetch: auth.wrap(async (request) => {
    const user = await auth.getUser(request)
    if (new URL(request.url).pathname.startsWith('/api/') && !user) {
      return Response.json({ error: 'Sign in required' }, { status: 401 })
    }
    return app.fetch(request)
  }),
}

See examples/cloudflare-worker.ts (static assets + API) and examples/hono.ts.

Next.js (App Router)

// lib/freebuff.ts
import { createFreebuffNext } from '@freebuff/auth/next'
export const freebuff = createFreebuffNext({
  clientId: process.env.FREEBUFF_CLIENT_ID!,
  baseUrl: process.env.APP_ORIGIN!,
})

// app/auth/freebuff/[...freebuff]/route.ts
export const { GET, POST } = freebuff.handlers

// proxy.ts (middleware.ts before Next 16): refreshes sessions before render
export const proxy = freebuff.proxy
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }

// Any Server Component, Route Handler or Server Action
const user = await freebuff.getUser()

See examples/nextjs.

Buttons and client state (React)

import { SignInWithFreebuff, SignOutButton, useFreebuffUser } from '@freebuff/auth/react'

<SignInWithFreebuff />                 // link to /auth/freebuff/signin, returns here
<SignOutButton returnTo="/" />         // a POST form; sign-out is never a link
const { user, isLoading } = useFreebuffUser() // reads /auth/freebuff/session

Without React: link to /auth/freebuff/signin?returnTo=/somewhere, sign out with <form method="post" action="/auth/freebuff/signout">, and read GET /auth/freebuff/session → { "user": { ... } | null }.

The user

interface FreebuffUser {
  sub: string            // always; stable for this visitor in this project
  email?: string         // `email` scope, when present
  email_verified?: boolean
  name?: string          // `profile` scope, when set
  picture?: string       // https URL
}
  • Key your data on sub. It is the same across your project's development and production clients, and different in every other project, so sites cannot correlate visitors by it. It never changes.
  • Email is profile data, not identity. It can change, may be absent, and may be unverified. Never merge or look up accounts by email.
  • Ask for less with scopes: ['openid'] (just sub) or ['openid', 'email'].

Sessions and security

  • No secrets anywhere in your project. Freebuff signs the tokens; your server verifies them with Freebuff's public keys (cached; no per-request network call).
  • The session is one __Host-freebuff_session cookie: HttpOnly, Secure, SameSite=Lax, host-only. It holds a one-hour access token and a refresh token. Tokens never reach page JavaScript.
  • Refresh tokens rotate on every use. A stolen, already-used refresh token ends the whole session. Parallel requests are safe: they receive the same successor.
  • Sessions last up to 30 days of inactivity and at most 90 days, then the visitor signs in again (usually one click: Freebuff remembers consent).
  • Sign-out is POST, same-origin only; it revokes the session at Freebuff.
  • If a Freebuff account is banned, or the project is disabled, its sessions stop within an hour (the access-token lifetime) and cannot be renewed.
  • Sign-in uses PKCE, state and nonce, pinned issuer, exact callback URLs, and RFC 9207 issuer checks. Token requests are refused from browsers.

getUser only refreshes inside wrap, the Next.js proxy, or the /auth/freebuff/session route, because a refreshed session must reach the browser or its next refresh looks like theft. Elsewhere it verifies the current access token and returns null once it expires.

Local development

Use your project's development client ID and allowInsecureLocalhost: true with a http://localhost:<port> or http://127.0.0.1:<port> baseUrl. Development clients accept any port on the registered loopback host. Never use a development client or allowInsecureLocalhost in production.

Options

| Option | Default | | | --- | --- | --- | | clientId | required | Public, from provisioning | | baseUrl | required | Canonical origin; never derived from requests | | issuer | https://freebuff.com/oidc | Only for a non-production Freebuff | | scopes | ['openid', 'email', 'profile'] | openid is always included | | allowInsecureLocalhost | false | Development only | | fetch | global fetch | Server-side calls to Freebuff |

For AI coding agents integrating this package, read AGENTS.md.