The authfy npm package
Reusable client for the Authfy OAuth2 / OIDC Authorization Code + PKCE flow, built with Vite + TypeScript. Ships ESM + CJS with full types.
Designed for Next.js (App Router) with a turn-key adapter, but the core is
framework-agnostic. Config and cookies are injected — the library never
reads process.env or touches document.cookie itself, so it is SSR/edge
safe.
Install
npm install authfy
Official package page: npmjs.com/package/authfy
For the React hooks (authfy/react) you also need react and react-dom
(peer dependencies). The package source lives in this repo under
react-package/.
Quick start (Next.js) — zero config
Two files in your app plus .env. That's it.
1. Set .env
The library reads these automatically — nothing is hardcoded:
APP_URL=http://localhost:3004
AUTH_API_URL=http://localhost:8081
AUTH_VIEW_URL=http://localhost:3001
AUTH_APP_ID=hrm
APP_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
2. Add one catch-all route file
Next.js routing is file-system based, so this one file must exist in your
app (a package in node_modules can't create it for you). It's a single line —
it handles start, callback, refresh, logout, and session for you:
// app/api/authy/[...authy]/route.ts
export { GET, POST } from "authfy/next/auto";
That's the entire server setup — no lib/authy.ts, no per-route files. Config
is read from .env, and the API/PKCE/refresh/logout are all wired internally.
3. Wrap the app
// app/layout.tsx
import { AuthyProvider } from "authfy/react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<AuthyProvider>{children}</AuthyProvider>
</body>
</html>
);
}
4. Use it in components
"use client";
import { useAuthy } from "authfy/react";
export function AccountBadge() {
const { user, authenticated, status, logout } = useAuthy();
if (status === "loading") return <span>Loading…</span>;
if (!authenticated) return <a href="/api/authy/start">Sign in</a>;
return <button onClick={() => logout()}>{user?.name ?? user?.email} — Sign out</button>;
}
Protect a subtree:
"use client";
import { AuthGuard } from "authfy/react";
export default function Protected({ children }: { children: React.ReactNode }) {
return <AuthGuard>{children}</AuthGuard>;
}
Tokens never reach browser JS — useAuthy() only exposes
user and authenticated. Tokens stay in the HTTP-only authy_session
cookie.
Silent refresh in the proxy
Next.js 16 replaced middleware.ts with proxy.ts (exporting proxy).
authy.proxy returns a redirect when the access token is about to expire, or
null to continue:
// proxy.ts (Next.js 16)
import { NextResponse, type NextRequest } from "next/server";
import { proxy as authyProxy } from "authfy/next/auto";
export function proxy(request: NextRequest) {
return authyProxy(request) ?? NextResponse.next();
}
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\..*).*)"] };
Read the verified session on the server
import { cookies } from "next/headers";
import { authy } from "authfy/next/auto";
export default async function Page() {
const store = await cookies();
const session = await authy().getServerSession({ get: (name) => store.get(name)?.value });
if (!session) return null;
return <pre>{session.scope}</pre>; // session.accessToken is verified, server-only
}
Call Authfy external APIs with that session:
const company = await authy().server.api.getExternalCompany(session); // { ok, status, data, error }
Explicit config (instead of auto)
If you'd rather inject config yourself — multi-tenant, a secrets manager, or
tests — skip authfy/next/auto and build the adapter once:
// lib/authy.ts
import { configFromEnv } from "authfy";
import { createAuthyNext } from "authfy/next";
export const authy = createAuthyNext({ config: configFromEnv(process.env) });
Then either re-export the catch-all dispatcher from one file:
// app/api/authy/[...authy]/route.ts
import { authy } from "@/lib/authy";
export const GET = authy.route.GET;
export const POST = authy.route.POST;
…or mount one file per route with authy.handlers.start, .callback,
.refresh, .logout, .session if you prefer them split out.
Configuration reference
createAuthyNext({ config, ... }) takes a config from
configFromEnv(process.env) or createAuthyConfig({...}):
| Field | Env var | Default |
|---|---|---|
appUrl | APP_URL | http://localhost:3004 |
authApiUrl | AUTH_API_URL | http://localhost:8081 |
authViewUrl | AUTH_VIEW_URL | http://localhost:3001 |
clientId | AUTH_APP_ID | authfy-app |
scope | AUTH_SCOPE | openid profile email |
appPublicKey | APP_PUBLIC_KEY | (empty — external API + token cover disabled) |
redirect_uri, the authorize URL, and the logout URL are derived
automatically. Extra options on createAuthyNext: routePrefix (default
/api/authy), logoutPath (default /signed-out), secureCookies (defaults
to true on https), and onTokenRefresh.
Token refresh logging
After every successful silent rotation the refreshed access token is logged:
- Server (refresh route):
[authfy] Refreshed access token: ...on the server console. - Browser (
AuthyProvider): the same line in the browser console after a POST refresh.
To override or silence it, switch to explicit config and pass
onTokenRefresh:
export const authy = createAuthyNext({
config: configFromEnv(process.env),
onTokenRefresh: (accessToken) => console.log("[authfy] Refreshed access token:", accessToken),
// or: onTokenRefresh: () => {} // silence it
});
Entry points
| Import | Use for |
|---|---|
authfy/next/auto | Zero-config Next.js: GET/POST catch-all + proxy, read from .env (start here) |
authfy/next | createAuthyNext({ config }) for explicit/injected config |
authfy/react | AuthyProvider, useAuthy, AuthGuard (client) |
authfy | framework-agnostic core: config, crypto, API client, types |
authfy/server | lower-level flow/codec/verifier if you're not on Next.js |
Local development & linking
npm run dev # vite build --watch -> dist/
npm run build # one-off build
Link into a consuming app with npm link authfy, or add both to a workspace
("workspaces": ["authfy", "apps/*"]) and depend on "authfy": "workspace:*".
Deployment note
If your app runs behind a reverse proxy (Nginx, CapRover, etc.) and the OAuth
callback returns 502 Bad Gateway while everything else works, the proxy's
response-header buffers are likely too small for the session Set-Cookie
headers. Raise proxy_buffer_size / proxy_buffers /
large_client_header_buffers on the proxy, clear cookies, and start a fresh
login — or ask your Authfy administrator for the recommended configuration.