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

@broberg/auth

v0.1.2

Published

Thin fleet wrapper around Better Auth — one createAuth() for email+password, magic-link (via @broberg/mail), social login (Google/Apple/GitHub/Microsoft/LinkedIn/Facebook) and passkey/WebAuthn, with dark-ship provider guards and Hono + Next mount helpers.

Readme

@broberg/auth

A thin fleet wrapper around Better Authone createAuth() for email + password, magic-link (delivered through @broberg/mail), social login (Google, Apple, GitHub, Microsoft, LinkedIn, Facebook) and passkey / WebAuthn.

It runs inside your own app, against your own database — no external service, no recurring cost, no vendor lock-in, EU-data stays where you host it. The wrapper adds the fleet's opinions on top of Better Auth: dark-ship provider guards, magic-link routed through @broberg/mail, and per-stack mount helpers (Hono + Next).

Mirrors @broberg/ai-sdk (a thin wrapper over the Vercel AI SDK). The heavy lifting — OAuth2/OIDC + PKCE, Apple's ES256/form_post quirks, the WebAuthn ceremony — is Better Auth's; this package is the fleet-shaped config surface over it.

Install

pnpm add @broberg/auth better-auth drizzle-orm
# optional, per method you enable:
pnpm add @broberg/mail            # magic-link delivery
pnpm add @better-auth/passkey     # passkey / WebAuthn

better-auth is a peer so your server wrapper and better-auth/client on the frontend share one pinned version. @broberg/mail, @better-auth/passkey, hono and next are optional peers — install only what you use.

Usage

import { createAuth } from "@broberg/auth";
import { drizzle } from "@broberg/auth";              // = Better Auth's drizzleAdapter
import { createMailer } from "@broberg/mail";

const auth = createAuth({
  database: drizzle(db, { provider: "sqlite" }),       // or "pg" / "mysql"
  baseURL: process.env.APP_URL,
  emailPassword: true,
  magicLink: { mailer: createMailer({ apiKey: process.env.RESEND_API_KEY, from: "..." }) },
  passkey: { rpID: "xrt81.com", rpName: "XRT81" },
  socials: {
    // Only the providers whose config is present REGISTER (dark-ship).
    google:    { clientId: env.GOOGLE_ID,    clientSecret: env.GOOGLE_SECRET },
    apple:     { clientId: env.APPLE_ID,     clientSecret: env.APPLE_SECRET },
    github:    { clientId: env.GITHUB_ID,    clientSecret: env.GITHUB_SECRET },
    microsoft: { clientId: env.MS_ID,        clientSecret: env.MS_SECRET },
    linkedin:  { clientId: env.LINKEDIN_ID,  clientSecret: env.LINKEDIN_SECRET },
    facebook:  { clientId: env.FB_ID,        clientSecret: env.FB_SECRET },
  },
});

Mount it

// Stack B — Hono
import { mountAuth } from "@broberg/auth/hono";
mountAuth(app, auth);                                  // GET+POST /api/auth/*

// Stack A — Next.js App Router  (app/api/auth/[...all]/route.ts)
import { toNextHandler } from "@broberg/auth/next";
export const { GET, POST } = toNextHandler(auth);

Dark-ship + login buttons

A provider with no secret is never registered and never crashes. Render buttons for exactly the enabled methods:

import { configuredMethods } from "@broberg/auth";
const m = configuredMethods(cfg);   // { google, apple, ..., magicLink, passkey, emailPassword }
// show the Google button only when m.google === true

Individual guards are exported too: googleConfigured, appleConfigured, githubConfigured, microsoftConfigured, linkedinConfigured, facebookConfigured, emailPasswordConfigured, magicLinkConfigured, passkeyConfigured.

Typed plugin api (magic-link / passkey) — createTypedAuth

createAuth dark-ships magic-link/passkey conditionally at runtime, so its return type can't statically know which plugins are present — plugin-augmented api.* methods (auth.api.signInMagicLink, the passkey endpoints) drop off the type. When you enable those and want them fully typed with no cast, use createTypedAuth and pass the plugins explicitly:

import { createTypedAuth, buildMagicLinkPlugin, buildPasskeyPlugin } from "@broberg/auth";

const auth = createTypedAuth(
  { database: drizzle(db, { provider: "sqlite" }), socials: { google }, emailPassword: true },
  [buildMagicLinkPlugin({ mailer }), buildPasskeyPlugin({ rpID: "xrt81.com", rpName: "XRT81" })],
);

await auth.api.signInMagicLink({ body: { email } });   // fully typed, no cast

Social providers + email/password still dark-ship; the plugins you pass are explicit (you opted in). createAuth is unchanged — use it when you don't need the plugin endpoints statically typed.

The createTypedAuth result mounts through mountAuth / toNextHandler with no cast — the mount helpers accept the structural slice they use, so the plugin-narrowed instance is accepted just like a createAuth one (F008.8).

MitID (and other custom IdPs) — deferred

MitID is not bundled. It is OIDC, but it requires a broker (Criipto / Signaturgruppen / Nets DanID) + a NemLog-in agreement + a certificate — real authority onboarding, not "add a provider". When that is in place, slot it in via Better Auth's Generic OAuth plugin:

import { genericOAuth } from "better-auth/plugins/generic-oauth";
createAuth({
  // ...
  plugins: [
    genericOAuth({ config: [{ providerId: "mitid", /* broker discoveryUrl + client creds */ }] }),
  ],
});

What this package does NOT own

  • DB schema / migrations — Better Auth owns its user/session/account tables; you run its migrations against your DB.
  • Session creation — Better Auth mints sessions; this wrapper only configures it.
  • Email templates — magic-link delivery routes through @broberg/mail (which owns delivery only); branded bodies are yours via the render option.

Versioning

Auth is prod-critical — exact-pin @broberg/auth (and better-auth) in production consumers. Published from broberg-ai/components via OIDC Trusted Publishing.