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

@tgoliveira/secure-auth

v0.13.0

Published

Opinionated Next.js + Drizzle + PostgreSQL authentication package (experimental internal release)

Readme

@tgoliveira/secure-auth

Version: see package.json (experimental — not production-ready)

Opinionated authentication package for Next.js App Router, TypeScript, Drizzle ORM, and PostgreSQL.


Composition root (read this first)

Consumers integrate exclusively through:

import { createSecureAuth } from "@tgoliveira/secure-auth/next";

export const secureAuth = createSecureAuth(config);
// secureAuth.routes.register.POST
// secureAuth.uiConfig → SecureAuthUIProvider

| Do | Don't | | --- | --- | | Create one secureAuth instance in app bootstrap | Import @tgoliveira/secure-auth/server (removed) | | Wire routes: secureAuth.routes.register.POST | Call createRoutes or createAuthServices | | Pass secureAuth.uiConfig to SecureAuthUIProvider | Call internal runtime helpers | | Map env vars in your app | Expect the package to read process.env |

Onboarding docs: configuration-reference.md · consumer-quick-start.md · minimal-consumer-example.md · package-api.md

In-repo validation app: apps/consumer-demo — minimal consumer with public exports only (see consumer-demo-validation.md).


UI provider

Page copy, paths, password policy defaults, and configured OAuth provider IDs come from config — not global state or client-side provider discovery.

// app/layout.tsx
import { SecureAuthUIProvider } from "@tgoliveira/secure-auth/react";
import { secureAuth } from "@/lib/secure-auth";

export default function RootLayout({ children }) {
  return (
    <SecureAuthUIProvider config={secureAuth.uiConfig}>
      {children}
    </SecureAuthUIProvider>
  );
}

Configure via createSecureAuth({ ui: { paths, messages, cssVariables, passwordStrength }, passwordPolicy: { minLength } }). Package pages use useSecureAuthUi() internally when wrapped in SecureAuthUIProvider config={secureAuth.uiConfig}. Default minimum password length is 12; map AUTH_PASSWORD_MIN_LENGTH in your app env into passwordPolicy.minLength to override (the package never reads env directly). The resolved policy is available as secureAuth.passwordPolicy and secureAuth.uiConfig.passwordPolicy.

secureAuth.uiConfig.oauthProviderIds contains only effective provider IDs. LoginPage, RegisterPage, and SocialSignIn render that final list synchronously without requesting /api/auth/providers. Missing UI config fails closed and renders no OAuth controls. For standalone composition, pass providerIds directly to SocialSignIn. Provider secrets and client IDs remain server-only.

Password strength and validation feedback render above password fields by default. Set ui.passwordStrength.position to "below" to restore legacy placement. The feedback region is stable from first render (neutral requirements before typing; strength updates in place without focus loss). See customization.md.


Install (consumer app)

npm install @tgoliveira/secure-auth@latest \
  next@^16.2.11 react@^19 react-dom@^19 next-auth@^4.24.15 drizzle-orm@^0.45.2

See publishing-npm-automation.md for automated npm releases and consumer-quick-start.md for install.

The package writes new password digests with server-only Argon2id. Keep @node-rs/argon2 in the consuming app's Next.js serverExternalPackages alongside postgres, bcryptjs, and @simplewebauthn/server; the full snippet is in the consumer quick start. Existing bcrypt accounts need no migration script or user action: a successful password login replaces the legacy digest atomically while preserving the password-change timestamp.


Supported public entry points

| Import | Purpose | | --- | --- | | @tgoliveira/secure-auth/next | createSecureAuth(config) — composition root | | @tgoliveira/secure-auth | Types, manifest-derived SECURE_AUTH_PACKAGE_VERSION, authSchema, safeLogger | | @tgoliveira/secure-auth/react | UI primitives, pages, SecureAuthUIProvider, SecureAuthUIPublicConfig | | @tgoliveira/secure-auth/react/client | Client-only UI, passkey sign-in, default sign-out | | @tgoliveira/secure-auth/client | Browser API client, passkey helpers | | @tgoliveira/secure-auth/client/password-policy | Password policy helpers | | @tgoliveira/secure-auth/drizzle/schema | Auth Drizzle schema | | @tgoliveira/secure-auth/email | EmailProvider types | | @tgoliveira/secure-auth/styles.css | Tailwind v4 source registration (CSS import) |

Unsupported: @tgoliveira/secure-auth/server, createRoutes, createAuthServices, deep src/** imports.

Full reference: package-api.md.


Quick start

import { createSecureAuth } from "@tgoliveira/secure-auth/next";
import type { EmailProvider } from "@tgoliveira/secure-auth/email";
import { db } from "@/lib/db";

const emailProvider: EmailProvider = {
  async send({ to, subject, html, text }) {
    // app-owned delivery (SMTP, console, etc.)
  },
};

export const secureAuth = createSecureAuth({
  db,
  app: { name: "My App", slug: "my-app", baseUrl: process.env.APP_BASE_URL! },
  auth: {
    afterLoginPath: "/dashboard",
    redirectAuthenticatedFromGuestPages: true,
    authenticatedRedirectPath: "/dashboard",
    afterLogoutPath: "/",
    requireEmailVerificationBeforeSignIn: false,
    nextAuthSecret: process.env.NEXTAUTH_SECRET!,
    twoFactorEncryptionKey: process.env.TWO_FACTOR_SECRET_ENCRYPTION_KEY!,
  },
  email: { from: "My App <[email protected]>", provider: emailProvider },
  webauthn: {
    rpId: process.env.WEBAUTHN_RP_ID!,
    rpName: "My App",
    origin: process.env.WEBAUTHN_ORIGIN!,
    originAliasPolicy: process.env.NODE_ENV === "production" ? "none" : "apex-www",
  },
  ui: {
    paths: { login: "/login", register: "/register" },
    messages: { loginTitle: "Sign in to My App" },
  },
});

webauthn.originAliasPolicy defaults to "apex-www" for compatibility, including the local localhost/127.0.0.1 pair. Set it to "none" when production redirects to one canonical host; only the primary origin and exact webauthn.origins entries are then accepted by assertion verification.

For login and package-owned exact-credential assertions, secure-auth presents internal before hybrid and adds advisory WebAuthn Level 3 local-first hints when both transports are available. Hybrid remains an allowed fallback, user verification remains required, and browsers without hints support continue from the same credential allow-list. No authenticator attachment is forced.

Consumers that compose an independent browser-only capability may set the server-only webauthn.getLoginAuthenticationExtensions({ userId, credentialIds }) callback. It can add only bounded JSON-safe WebAuthn extension inputs after the account allow-list is resolved; it cannot replace the challenge, RP ID, user-verification policy, or credentials. Pair it with PasskeyLoginHooks.prepareOptions and the typed onFullyAuthenticated result. See passkey-credential-interoperability.md.

Deterministic cross-device vault unlock should use the separate, opt-in portable broker grant module. It runs a dedicated UV-required assertion after full account authentication, signs a short-lived ES256 grant, and verifies/consumes broker completion receipts without importing vault-core or receiving PUK/PRF/UVK material. See portable-vault-grants.md.

Route handlers

Thin App Router wrappers in the consuming app:

// src/app/api/auth/register/route.ts
import { secureAuth } from "@/lib/secure-auth";

export const POST = secureAuth.routes.register.POST;

All handlers: secureAuth.routes.*. Route map: package-api.md.

Tailwind CSS (v4)

@import "tailwindcss";
@import "@tgoliveira/secure-auth/styles.css";

Define :root CSS variables for theme tokens (see starter globals.css).

EmailProvider

Account email flows through your injected provider only. Transport lives in the app; templates use config.app.name and config.app.baseUrl.

Reference: apps/dev-harness/src/modules/email/core/ + apps/dev-harness/src/lib/secure-auth.ts.

Configuration

The package does not read runtime environment variables. Map secrets at the app boundary in createSecureAuth(config).

Canonical reference: configuration-reference.md — every env variable, TypeScript config path, defaults, and parsing rules.

Services receive config and db via constructor injection — there is no global runtime state.

See architecture.md.

Optional session policy — multiple concurrent sessions is the default. Enable single active session per user via env or config:

AUTH_SINGLE_ACTIVE_SESSION=true
createSecureAuth({
  sessions: { singleActiveSession: true },
});

See customization.md and security.md.

When singleActiveSession is enabled, other sessions are revoked only after final login completion (including post-TOTP verification for passkey and credentials logins).

Passkeys and two-factor authentication

Passkey sign-in is a primary authentication method. When TOTP 2FA is enabled on the account, passkey verification creates a pending login challenge (same httpOnly cookie and /login/2fa flow as email/password). The session is finalized only after valid TOTP verification — passkeys do not bypass app-level 2FA.

Capability boundaries: account security settings list passkeys with signInEnabled / vaultUnlockEnabled metadata. Only pure account sign-in passkeys are removable from account settings; vault-only and dual-capability credentials are protected at the API and UI. Existing vault-only credentials can be explicitly enabled for sign-in with an exact UV-required proof; they are never silently upgraded.

Optional browser-only preparation and verified callbacks let a consumer compose another capability without making secure-auth depend on that package. Secure-auth strips PRF client-extension results before serialization and rejects them at every account verification route. See consumer-passkey-capability-boundaries.md, passkey-registration-capability-boundary-audit.md, and passkey-credential-interoperability.md. Portable broker adoption and migration 0005_nasty_slipstream.sql are documented in portable-vault-grants.md.

CAPTCHA (Cloudflare Turnstile)

Optional bot protection for credentials registration and login. Disabled by default.

createSecureAuth({
  captcha: {
    enabled: true,
    provider: "turnstile",
    siteKey: "...",
    secretKey: "...",
    pages: { register: true, login: true },
  },
});

Server-side Siteverify validation is mandatory when enabled. Only siteKey and page flags are exposed via uiConfig; secretKey stays server-only. OAuth and passkey flows are not CAPTCHA-protected in this release.

Magic link

Optional passwordless email login. Disabled by default.

createSecureAuth({
  auth: {
    magicLink: { enabled: true },
  },
});

When enabled, the login page shows a Sign in with email link option. The user receives a single-use link valid for 15 minutes. If the account has 2FA enabled, magic link verification creates a pending 2FA challenge (same flow as credentials login). Rate limit: 3 requests per email per 10 minutes. Request responses are anti-enumeration safe.

Wire routes: magicLinkRequest.POST/api/auth/magic-link/request, magicLinkVerify.POST/api/auth/magic-link/verify. Email links point to LoginMagicLinkPage (/login/magic-link by default), which POSTs the token to the verify API.

Generic password components

Reusable password policy UI for non-auth flows (vault password, encryption password, etc.).

import {
  PasswordStrengthField,
  PasswordSetupFields,
} from "@tgoliveira/secure-auth/react/client";

Validation helpers live in @tgoliveira/secure-auth/client/password-policy. The package never reads env — map consumer env to a policy object and pass it as props.

See generic-password-components.md.

OAuth providers

Optional social sign-in via NextAuth v4. Map credentials in your app — the package never reads process.env:

createSecureAuth({
  oauth: {
    google: { clientId, clientSecret },
    apple: { clientId, clientSecret },
    github: { clientId, clientSecret },
    microsoft: { clientId, clientSecret, tenantId },
  },
});

Supported provider ids: google, apple, github, azure-ad (Microsoft). Buttons render on the first client frame only when the matching effective config is present in secureAuth.uiConfig.oauthProviderIds. GitHub callback: {APP_BASE_URL}/api/auth/callback/github. See configuration-reference.md.

Database

  • Schema: @tgoliveira/secure-auth/drizzle/schema
  • Migrations: shipped in package migrations/ folder
  • App owns DATABASE_URL; package owns auth tables

Shared-credential deployments must apply 0004_outgoing_william_stryker.sql before deploying the corresponding package. Vault-only to sign-in promotion is hidden by default and requires the explicit page/component promotion prop in addition to mounting the route.

See migrations.md and consumer-quick-start.md.

Versioning

| Range | Meaning | | --- | --- | | 0.1.x | Experimental internal | | 0.2.x | DB contract may break; API stabilization | | 1.0.0 | Production-ready contract |

Security

See security.md for passwords, tokens, HIBP breach detection, and security notifications. Not production-ready at 0.2.x.

Before releases: npm run audit:security from the monorepo root. Details: dependency-audit.md.

License

MIT © 2026 Thiago Oliveira. See LICENSE.