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

@growth-labs/auth

v0.7.3

Published

OpenAuth **client** package for Astro + Cloudflare. Verifies JWTs, manages session cookies, gates protected paths, issues a signed server-readable identity cookie, and injects login/logout/callback routes.

Readme

@growth-labs/auth

OpenAuth client package for Astro + Cloudflare. Verifies JWTs, manages session cookies, gates protected paths, issues a signed server-readable identity cookie, and injects login/logout/callback routes.

This is a client only. It does NOT run an OpenAuth issuer. The canonical issuer runtime lives in growth-labs/identity-platform; apps/auth-server/ in this repo is only a minimal sample Worker.

Identity, not authorization. Answers "who is this user?" — not "what can they access?" Roles, entitlements, and premium checks are consumer concerns (e.g. @fulcrum/auth).

Config

import auth from '@growth-labs/auth'

auth({
  issuer: 'https://auth.fedweek.com',           // Browser-facing OpenAuth issuer URL
  issuerInternal: 'https://auth-internal.example.com', // Optional back-channel issuer URL
  clientId: 'fedweek-prod',                      // Optional; defaults to request hostname
  clientSecret: 'AUTH_CLIENT_SECRET',            // Worker secret binding name, resolved at request time
  resource: 'https://fedweek.com',               // RFC 8707 resource/audience URI
  cookiePrefix: 'fedweek',                       // → fedweek_at, fedweek_rt, fedweek_provider cookies
  sessionCookieDomain: '.fedweek.com',           // Cross-subdomain sharing (optional)
  gatedPaths: ['/premium/*', '/account/*'],      // Glob patterns for protected routes
  loginPath: '/login',                           // Redirect target for unauthenticated users
  logoutRedirect: '/',
  callbackPath: '/api/auth/callback',
  passwordPath: '/password',                     // Local branded password form when renderers.password is set
  verifyCodePath: '/verify-code',                // Local branded magic-code form when renderers.verifyCode is set
  providers: ['google', 'password', 'email-code'],
  loginPage: {
    title: 'Sign in',
    brandName: 'FEDweek',
    logoUrl: 'https://media.fedweek.com/logos/header.png',
  },
  renderers: {
    // Optional all-or-nothing UI overrides per auth surface.
    // Flow logic, cookies, callback exchange, and issuer URLs remain package-owned.
    login: { module: '/src/auth/auth-render', export: 'renderLogin' },
    password: { module: '/src/auth/auth-render', export: 'renderPassword' },
    verifyCode: { module: '/src/auth/auth-render', export: 'renderVerifyCode' },
  },
  accessTokenMaxAge: 900,                        // 15 min (seconds)
  refreshTokenMaxAge: 2592000,                   // 30 days
})

What It Injects

Middleware (runs on every request):

  1. Reads {cookiePrefix}_at and {cookiePrefix}_rt cookies
  2. Verifies access token JWT against the internal issuer's JWKS ({issuerInternal ?? issuer}/.well-known/jwks.json, cached in Worker memory)
  3. If expired but refresh token valid → silent refresh, set new cookies
  4. Populates context.locals.user as User | null
  5. If path matches gatedPaths and user is null → redirect to loginPath

Routes:

  • GET /login — provider-selection page; ?provider=google|password|email-code starts auth locally and redirects into the issuer's generic /authorize endpoint
  • GET /logout — clears cookies, revokes refresh token at the internal issuer, redirects
  • GET /api/auth/callback — OAuth callback handler (PKCE code exchange, cookie setting)
  • GET /forgot-password / GET /reset-password — local redirects to issuer-owned password UI unless renderers are configured
  • GET /password / GET /verify-code — local branded password and magic-code views when renderers.password / renderers.verifyCode are configured
  • POST /api/auth/password/verify, /api/auth/code/request, /api/auth/code/verify, /api/auth/password/reset/request, /api/auth/password/reset/confirm — package-owned back-channel form handlers that call the issuer JSON API through issuerInternal, then return a 303 browser redirect or a JSON redirect contract for Accept: application/json requests

Branded Auth UI

Consumers can supply renderers.login, renderers.forgotPassword, and renderers.resetPassword in auth() options. Consumers that want every password and magic-code step to stay on their own domain can also supply renderers.password and renderers.verifyCode. Each renderer receives a typed payload from @growth-labs/auth/types with package-built local form actions, provider or issuer URLs, and brand values, then returns either an HTML string or a Response.

Renderers replace only GET form views. Callback, logout, token exchange, cookies, PKCE/state, POST CSRF checks, and issuer communication remain package-owned. When renderers.password or renderers.verifyCode is configured, /login links those provider buttons to the local branded routes; otherwise it keeps the existing issuer-hosted OpenAuth UI flow.

Forgot/reset renderers receive both the historical issuer URL and the new local API form target. Use passwordResetRequestUrl and passwordResetConfirmUrl for consumer-hosted reset forms.

Package-owned POST routes are safe for native forms and fetch-enhanced renderers. Native forms receive 303 See Other redirects. Fetch callers should send Accept: application/json; success returns { ok: true, redirect }, and errors return { error, redirect } with a 400 status. POST requests without a same-origin Origin header are rejected with 403 before the issuer is called.

Server SDK

import { authClient } from '@growth-labs/auth/server'

const client = authClient(context)

await client.password.verify({ email, password })
await client.code.request({ email })
await client.code.verify({ email, code })
await client.password.reset.request({ email })
await client.password.reset.confirm({ token, newPassword })

authClient(context) reads the resolved integration config and Worker bindings, posts to {issuerInternal ?? issuer}/api/v1/auth/*, resolves clientSecret binding names at request time, sends client_secret_basic for confidential clients, and includes client_id, redirect_uri, PKCE code_challenge, state, and resource for authorization-code issuing calls. Password reset request calls also include reset_url_base from the current consumer origin so the issuer can send reset links back to the same registered host. The package-owned POST routes use this SDK directly; consumers can also use it from custom server routes.

Analytics Events

When @growth-labs/[email protected] or newer is installed, the callback route emits signup_completed or login_completed through trackServerEvent from @growth-labs/analytics/utils. The payload is:

{
  provider: 'google' | 'password' | 'email-code' | 'unknown',
  isNewUser: boolean,
  auth_path: string,
  identity_user_id: string,
}

The event is emitted from the consuming site request context, so analytics can preserve the site's anonymous visitor_id, session_id, attribution cookies, and gl_identity_links row. The identity platform does not need to see site attribution cookies.

Using context.locals.user

// Gated page — user guaranteed authenticated
const { user } = Astro.locals
// user.id, user.email, user.name, user.avatarUrl

// Non-gated page — check first
if (user) {
  // show personalized content
}

TypeScript

interface User {
  id: string
  email: string
  name?: string | null
  avatarUrl?: string | null
}

// Available as User | null on context.locals.user (Astro.locals.user in .astro files)

Wrangler Bindings

GL_IDENTITY_SECRET is required when analytics dashboards need logged-in vs anonymous segmentation. It must be a 32-byte secret encoded with standard base64 and should be unique per tenant.

Injected routes read Worker bindings from cloudflare:workers, matching Astro 6 + @astrojs/cloudflare v13. They do not rely on Astro.locals.runtime or context.locals.runtime.

The issuer needs KV + D1 — see growth-labs/identity-platform for the production runtime shape, or apps/auth-server/README.md here for the minimal sample.

Integration Order

Auth middleware MUST run before consent and analytics middleware. List auth() first in astro.config.mjs integrations array.

Issuer Unreachable (Cold Start)

JWKS cached in Worker memory. If issuer unreachable on cold start:

  • Non-gated paths: fail open — user = null, page serves normally
  • Gated paths: fail closed — redirect to loginPath

There is no public runtime/admin API for evicting the JWKS cache. Cache state follows the Worker isolate lifecycle; _resetJwksCache() is an internal package-test helper and is not exported from @growth-labs/auth/utils.

OAuth Flow Summary

  1. User clicks a provider on the local login page → browser hits /login?provider=... or a local branded /password / /verify-code route when that renderer is configured
  2. The local /login start route stores PKCE/state/redirect/provider cookies and redirects to {issuer}/authorize?provider=...; configured resource is sent as the RFC 8707 resource indicator. Consumer-hosted /password, /verify-code, and /forgot-password pages seed root-scoped flow cookies locally, with the PKCE cookie visible to their package-owned POST handlers. The forgot-password route does not set a provider cookie.
  3. For consumer-hosted password or magic-code flows, package-owned POST routes call {issuerInternal ?? issuer}/api/v1/auth/* and redirect to /api/auth/callback?code=...&state=...; native forms receive 303 redirects, while Accept: application/json callers receive { ok/error, redirect }
  4. Issuer authenticates or issues a JSON authorization code → callback exchanges code at {issuerInternal ?? issuer}/token via PKCE, sends Authorization: Basic base64(client_id:client_secret) when clientSecret is configured, includes resource, reads properties.isNewUser from the JWT payload with fallback to top-level isNewUser for legacy issuers, uses the {cookiePrefix}_provider auth-flow cookie set at login start for provider attribution, sets session cookies, sets gl_id when GL_IDENTITY_SECRET is configured, and redirects to the original page

Signed Identity Cookie

gl_id lets server-side analytics distinguish logged-in requests without storing PII in Workers Analytics Engine and without trusting a client-writable flag.

  • Format: <base64url(payload)>.<base64url(hmac_sha256(payload_bytes, secret_bytes))> with no padding
  • Payload: lowercase hex sha256(sub + GL_IDENTITY_SECRET), where sub is the access-token subject. The raw subject is never written into the cookie.
  • Secret: GL_IDENTITY_SECRET, a per-tenant 32-byte standard-base64 value
  • Attributes: HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000
  • Issued: callback success after token exchange
  • Cleared: /logout with Max-Age=0

Verification helper:

import { verifyIdentityCookie } from '@growth-labs/auth/utils'

const result = await verifyIdentityCookie(cookieValue, env.GL_IDENTITY_SECRET)
if (result.valid) {
  console.log(result.payload) // 64-char opaque hex digest
}

signIdentityCookie(payload, secret) and verifyIdentityCookie(cookieValue, secret) use Web Crypto (crypto.subtle) and are async. Cookie issuance hashes the token subject before signing it.

Analytics Integration

Optional analytics helpers exist for login_completed / signup_completed, but the package does not currently guarantee WAE writes by itself. The event name is derived from the access-token JWT isNewUser claim, preferring payload.properties.isNewUser and falling back to top-level payload.isNewUser for legacy issuers. Consumers that need dashboard events should use the public analytics client/server contract available in their app.

Key Patterns

  • Virtual module: virtual:growth-labs/auth/config (slash, not hyphen)
  • Cookie names: {cookiePrefix}_at, {cookiePrefix}_rt, {cookiePrefix}_pkce, {cookiePrefix}_state, {cookiePrefix}_redirect, {cookiePrefix}_provider, gl_id
  • PKCE flow with code_verifier / code_challenge (S256); {cookiePrefix}_pkce is scoped to callbackPath for issuer redirect flows and to / for consumer-hosted password/code/forgot flows that post before callback
  • issuer is browser-facing; optional issuerInternal is used for token exchange, silent refresh, JWT verification/JWKS lookup, and revocation. If omitted, issuerInternal falls back to issuer.
  • Confidential OAuth clients use client_secret_basic on token exchange and refresh when clientSecret is configured. Prefer a Worker secret binding name such as AUTH_CLIENT_SECRET; runtime code resolves that binding before calling the issuer.
  • RFC 8707 resource is sent on authorize, token exchange, and refresh when configured
  • email-code is a client-side alias for the issuer's OpenAuth code provider
  • CSRF state parameter verified on callback
  • Access-token user claims live under payload.properties.*; the client falls back to top-level payload.email, payload.name, payload.image, and payload.isNewUser for legacy issuers
  • Forgot/reset password routes can remain issuer-owned redirects or use renderer-provided local API form targets
  • Package-owned auth POST routes require a same-origin Origin header; missing or cross-site form submissions return 403
  • Branded auth screens use renderers, not consumer-local shadow routes
  • No aud claim validation in this package — single client per issuer assumed
  • .astro component files ship as source, not compiled