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

@jfinvesting/auth-client

v0.1.14

Published

TypeScript OAuth2 client SDK for the jfinvesting auth-core service.

Readme

@jfinvesting/auth-client — TypeScript OAuth2 client for auth-core

A typed OAuth2 + OIDC client SDK for the auth-core service. Provides PKCE flow helpers, JWT verification (RS256 via JWKS), and ready-made adapters for Next.js (App Router), Express, and Fastify. Mirrors the Go client at github.com/jfinvesting/auth-client-go in error codes and the verified User shape.

  • Pure Web Crypto (works in Node 20+, Edge runtime, Cloudflare Workers).
  • One stable error type (AuthError) with stable codes.
  • Cookie-based session for Next.js, Bearer-token middleware for Node servers.

Install

npm i @jfinvesting/auth-client jose

jose is a runtime dependency. Framework adapters declare optional peer deps on next, react, express, fastify — only install what you use.

Quick start (Next.js App Router)

Four files. Copy-paste, set your env, done.

1. Mount the auth endpointsapp/auth/[...jfinvesting]/route.ts:

export { GET, POST } from "@jfinvesting/auth-client/next/route";

This exposes /auth/login, /auth/callback, /auth/logout, /auth/refresh, /auth/me.

2. Gate non-public routesmiddleware.ts:

import { authMiddleware } from "@jfinvesting/auth-client/next/middleware";

export default authMiddleware({ publicRoutes: ["/", /^\/auth\/.*/] });
export const config = { matcher: ["/((?!_next|favicon).*)"] };

3. Read the user from a server componentapp/page.tsx:

import { getServerUser } from "@jfinvesting/auth-client/next/server";

export default async function Home() {
  const user = await getServerUser();
  return <main>{user ? `Cześć, ${user.name}` : <a href="/auth/login">Zaloguj</a>}</main>;
}

4. .env.local:

AUTH_ISSUER=https://auth.jfinvesting.pl
AUTH_CLIENT_ID=cli_...
AUTH_CLIENT_SECRET=...
AUTH_REDIRECT_URI=http://localhost:3000/auth/callback

See examples/next-app-router/ for a runnable project.

Framework support

Next.js App Router — full support

Route mount + middleware + RSC helpers + client hook. The cookie session is managed for you, including PKCE state and refresh-token rotation.

// app/protected/page.tsx
import { requireUser } from "@jfinvesting/auth-client/next/server";
export default async function Protected() {
  const user = await requireUser();
  return <main>Hello {user.name}</main>;
}

Client subtree:

"use client";
import { AuthProvider, useAuth } from "@jfinvesting/auth-client/next/client";

Next.js Pages Router — manual integration

The mounted route handlers use the App Router NextRequest/NextResponse APIs; they do not drop into pages/api/auth/*.ts. Hand-roll the endpoints using the core helpers (authorizeUrl, exchangeCode, verifyAccessToken, generateVerifier). The shape mirrors examples/express/ — read it as a structural reference and translate to your pages/api/... files.

Express — Bearer middleware

import express from "express";
import { verifyBearer, requireRoles } from "@jfinvesting/auth-client/node/express";

const app = express();
app.get("/api/me", verifyBearer(), (req, res) => res.json({ user: req.user }));
app.get("/api/admin", verifyBearer(), requireRoles("admin"), (req, res) => res.json({ ok: true }));

verifyBearer reads Authorization: Bearer <jwt>. See examples/express/ for the full PKCE login flow.

Fastify — plugin

import Fastify from "fastify";
import { authPlugin } from "@jfinvesting/auth-client/node/fastify";

const app = Fastify();
await app.register(authPlugin, { protectAll: false });
app.get("/api/me", { preHandler: app.verifyAuth }, async (req) => ({ user: req.user }));

Pass { protectAll: true } to gate every request via an onRequest hook.

Configuration

Configuration comes from environment variables, parsed by readConfig().

| Variable | Required | Description | | --- | --- | --- | | AUTH_ISSUER | yes | URL of auth-core (e.g. https://auth.jfinvesting.pl). Trailing slashes are stripped. | | AUTH_CLIENT_ID | yes | OAuth client ID registered in auth-core. | | AUTH_CLIENT_SECRET | conditional | Required for confidential clients; omit for public PKCE-only clients. | | AUTH_REDIRECT_URI | yes | Callback URL — must exactly match a redirect_uri registered in auth-core. | | AUTH_COOKIE_DOMAIN | no | Optional cookie domain (e.g. .example.com) for cross-subdomain SSO. |

You can also build an AuthConfig programmatically and pass it to adapters (verifyBearer(cfg), app.register(authPlugin, { cfg })).

API reference

@jfinvesting/auth-client (root)

readConfig(env?: NodeJS.ProcessEnv): AuthConfig

Reads AUTH_* vars. Throws AuthError("auth/invalid-token") listing the missing variables.

generateVerifier(): string

Returns a fresh PKCE code_verifier (43-char base64url, RFC 7636).

challenge(verifier: string): Promise<string>

Computes the S256 PKCE code_challenge.

authorizeUrl(cfg, { state, verifier, scope? }): Promise<string>

Builds the GET /oauth/authorize URL. scope defaults to "openid email profile".

exchangeCode(cfg, { code, verifier }): Promise<TokenResponse>

Exchanges an authorization code for tokens.

  • Throws AuthError("auth/network") on transport failure.
  • Throws AuthError("auth/invalid-token") on a non-2xx token response.

refresh(cfg, refreshToken): Promise<TokenResponse>

Exchanges a refresh token for a new triple. Same throw contract as above. Refresh tokens rotate — the previous one is single-use.

verifyAccessToken(cfg, token): Promise<AuthUser>

Verifies signature (RS256), iss, aud, exp against the JWKS at <issuer>/.well-known/jwks.json. Throws:

  • auth/missing-token if token is empty
  • auth/expired-token on exp past
  • auth/unknown-issuer on iss mismatch
  • auth/invalid-token on any other validation failure

getJwks(issuer): RemoteJWKSet

Memoized jose JWKS resolver. Same instance is reused per-issuer so jose caches the keys.

AuthError, AuthErrorCode, AuthUser, AuthConfig, TokenResponse

See the Errors section and source for fields.

@jfinvesting/auth-client/next/route

GET(req: NextRequest) / POST(req: NextRequest)

App Router handlers. Mount under a catch-all segment, e.g. app/auth/[...jfinvesting]/route.ts. Dispatches on the trailing path:

| Path | Method | Behavior | | --- | --- | --- | | /auth/login | GET | Sets PKCE cookie, redirects to authorize URL. Honors ?next=/some/path. | | /auth/callback | GET | Validates state, exchanges code, sets access + refresh cookies, redirects to returnTo. | | /auth/me | GET | Returns { user } from the access cookie or 401. | | /auth/logout | GET, POST | Clears cookies, returns 204. | | /auth/refresh | GET, POST | Rotates tokens via the refresh cookie. |

@jfinvesting/auth-client/next/middleware

authMiddleware(opts?: { publicRoutes?: (string | RegExp)[] })

Returns a Next middleware. Strings ending in / match as a prefix; RegExps test against pathname. The mounted /auth/* endpoints are always public. Behavior:

  • No access cookie → 302 to /auth/login?next=<original>
  • Invalid/expired cookie → 302 to /auth/refresh?next=<original>
  • Valid cookie → NextResponse.next()

@jfinvesting/auth-client/next/server

getServerUser(): Promise<AuthUser | null>

Reads cookies via next/headers, verifies, returns the user or null.

requireUser(): Promise<AuthUser>

Like getServerUser but redirect("/auth/login") if missing/invalid.

@jfinvesting/auth-client/next/client (client component)

<AuthProvider initialUser?={AuthUser | null}>

Wraps a client subtree, fetches /auth/me on mount.

useAuth(): { user, isLoading, logout }

Must be used under <AuthProvider>. logout() POSTs /auth/logout then navigates to /.

@jfinvesting/auth-client/node/express

verifyBearer(cfg?: AuthConfig): RequestHandler

Verifies Authorization: Bearer <jwt> and sets req.user. On failure responds 401 with { error: AuthErrorCode, message }.

requireRoles(...roles: string[]): RequestHandler

Run after verifyBearer. 403 with auth/insufficient-role if the user lacks any of the listed roles. With no roles, just asserts authentication.

@jfinvesting/auth-client/node/fastify

authPlugin (default + named export)

Fastify plugin (wrapped with fastify-plugin).

await app.register(authPlugin, { cfg?, protectAll? });
  • Decorates request.user (typed AuthUser | undefined).
  • Decorates fastify.verifyAuth (a preHandler-shaped function) for per-route attachment.
  • With protectAll: true, also installs an onRequest hook that 401s every unauthenticated request.

Errors

All operations throw a single typed AuthError with a stable code. Switch on code, not on message.

| Code | When | | --- | --- | | auth/missing-token | No bearer header / empty access cookie / empty token passed to verifyAccessToken. | | auth/invalid-token | JWT signature/claim check failed; token endpoint returned a non-2xx; required env vars missing. | | auth/expired-token | JWT exp is in the past. | | auth/insufficient-role | requireRoles(...) did not find a matching role on the user. | | auth/unknown-issuer | JWT iss does not match cfg.issuer. | | auth/network | Token endpoint unreachable (DNS, TCP, TLS, etc.). |

These codes are kept in lockstep with the Go client (github.com/jfinvesting/auth-client-go) and the auth-core service spec.

Troubleshooting

JWKS fetch fails (network/firewall, wrong issuer). The verifier loads keys from <AUTH_ISSUER>/.well-known/jwks.json. If you see auth/invalid-token with a cause mentioning fetch, confirm the issuer URL is reachable from the runtime (Edge functions in particular cannot reach private networks), and that there is no trailing slash mismatch.

Refresh tokens are one-shot. Every successful refresh() call rotates the refresh token — do not reuse the previous one. The Next.js /auth/refresh endpoint already handles rotation; if you call refresh() yourself, persist the new refresh_token from the response.

Clock skew. jose allows ~30s default tolerance. If you observe sporadic auth/expired-token on freshly issued tokens, fix the clock on either the auth-core host or the consumer host (NTP) rather than widening the tolerance.

Edge runtime / secure cookie flag. The cookie helpers set secure based on process.env.NODE_ENV === "production". In dev over plain HTTP the cookie is non-secure (correct). In production, ensure NODE_ENV is actually production so the browser keeps the cookie HTTPS-only.

Pages Router. Unsupported as drop-in. Use the core helpers (authorizeUrl, exchangeCode, verifyAccessToken) inside your pages/api/auth/*.ts files; the Express example shows the structure.

License

MIT — see LICENSE.