authfy
v0.1.3
Published
Client for the Authy OAuth2/OIDC + PKCE flow, with optional React and server (Next.js) adapters.
Maintainers
Readme
authfy
Reusable client for the Authy 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 authfyOfficial npm package
https://www.npmjs.com/package/authfy?activeTab=readmeFor the React hooks (authfy/react) you also need react and react-dom (peer deps).
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.
Need to inject config explicitly (multi-tenant, secrets manager, tests)? Use
createAuthyNext({ config })instead — see Explicit config.
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, authenticated }. They stay in the HTTP-only authy_session cookie.
Optional: 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|.*\\..*).*)"] };Optional: 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 Authy 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), onTokenRefresh (see below).
Token refresh logging
After every successful silent rotation the refreshed access token is logged, matching the support console:
- Server (refresh route):
[authfy] Refreshed access token: <token>on the server console. - Browser (
AuthyProvider): the same line in the browser console after a POST refresh.
The default is exactly that log line. To override or silence it, switch to the
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 buildLink into a consuming app with npm link authfy, or add both to a workspace
("workspaces": ["authfy", "apps/*"]) and depend on "authfy": "workspace:*".
Publishing
npm publish # prepublishOnly runs the build; only dist/ + README are shipped