AAuthfy Docs

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({...}):

FieldEnv varDefault
appUrlAPP_URLhttp://localhost:3004
authApiUrlAUTH_API_URLhttp://localhost:8081
authViewUrlAUTH_VIEW_URLhttp://localhost:3001
clientIdAUTH_APP_IDauthfy-app
scopeAUTH_SCOPEopenid profile email
appPublicKeyAPP_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:

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

ImportUse for
authfy/next/autoZero-config Next.js: GET/POST catch-all + proxy, read from .env (start here)
authfy/nextcreateAuthyNext({ config }) for explicit/injected config
authfy/reactAuthyProvider, useAuthy, AuthGuard (client)
authfyframework-agnostic core: config, crypto, API client, types
authfy/serverlower-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.