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

@meet_patel_03/authflow-node

v0.1.2

Published

AuthFlow server-side SDK — token exchange and verification for Next.js and Express

Readme

@meet_patel_03/authflow-node

Server-side SDK for AuthFlow — token exchange, verification, and middleware for Node.js/Express and Next.js. Handles authorization code exchange, token refresh, id_token verification, and Bearer token validation.

Installation

npm install @meet_patel_03/authflow-node

Requirements:

  • Node.js 16+
  • jsonwebtoken (included as a dependency)
  • Express or Next.js (for middleware)
  • Session store: express-session, cookies, or similar (see examples below)

Quick start — Express

import express, { json } from "express";
import session from "express-session";
import {
  AuthFlowNodeClient,
  generateCodeVerifier,
  generateCodeChallenge,
  generateState,
  createExpressMiddleware,
  createSessionRefreshMiddleware,
  requireAuth,
  AuthFlowNodeError,
} from "@meet_patel_03/authflow-node";

// Configure session store (example: memory store for dev, use proper store in production)
const app = express();
app.use(json());
app.use(
  session({
    secret: "your-session-secret",
    resave: false,
    saveUninitialized: true,
    cookie: { secure: process.env.NODE_ENV === "production" },
  }),
);

// Initialize AuthFlow client
const authflow = new AuthFlowNodeClient({
  domain: "https://your-authflow-domain.com",
  clientId: "af_your_client_id",
  clientSecret: process.env.AUTHFLOW_SECRET!,
  redirectUri: "https://your-app.com/callback",
  scope: "openid profile email", // optional
});

// GET /login — Start the OAuth2 authorization flow
app.get("/login", (req, res) => {
  try {
    const verifier = generateCodeVerifier();
    const challenge = generateCodeChallenge(verifier);
    const state = generateState();

    // Store PKCE parameters in session
    req.session.pkce = { verifier, state };

    // Redirect to AuthFlow Universal Login
    const authUrl = authflow.buildAuthorizeUrl({
      state,
      codeChallenge: challenge,
      codeChallengeMethod: "S256",
      screen_hint: req.query.signup ? "signup" : "login", // optional
    });

    res.redirect(authUrl);
  } catch (err) {
    console.error("Login error:", err);
    res.status(500).json({ error: "login_failed" });
  }
});

// GET /callback — Exchange authorization code for tokens
app.get("/callback", async (req, res) => {
  try {
    const { code, state, error } = req.query;

    // Check for OAuth2 error response
    if (error) {
      return res.status(400).json({
        error,
        error_description: req.query.error_description,
      });
    }

    // Validate state parameter (CSRF protection)
    const { verifier, state: storedState } = req.session.pkce || {};
    if (state !== storedState) {
      return res.status(400).json({ error: "state_mismatch" });
    }

    if (!code || !verifier) {
      return res.status(400).json({ error: "missing_code_or_verifier" });
    }

    // Exchange code for tokens
    const tokens = await authflow.exchangeCode(code as string, verifier);

    // Store tokens in session (or httpOnly cookie)
    req.session.tokens = tokens;
    delete req.session.pkce; // Clean up

    res.redirect("/dashboard");
  } catch (err) {
    if (err instanceof AuthFlowNodeError) {
      console.error("Token exchange error:", err.error, err.errorDescription);
    }
    res.status(400).json({
      error: "callback_failed",
      error_description: err instanceof Error ? err.message : "Unknown error",
    });
  }
});

// GET /dashboard — Protected route
app.get(
  "/dashboard",
  createSessionRefreshMiddleware(authflow),
  requireAuth("/login"),
  (req, res) => {
    const user = req.session?.tokens ? " (authenticated)" : "";
    res.json({
      message: `Welcome to dashboard${user}`,
      tokens: req.session?.tokens ? "stored" : "none",
    });
  },
);

// GET /api/me — Protected API endpoint with Bearer token validation
app.get(
  "/api/me",
  createExpressMiddleware(authflow, {
    publicKeyOrSecret: process.env.OIDC_PUBLIC_KEY,
    credentialsRequired: true,
  }),
  (req, res) => {
    // req.authUser is populated by the middleware
    res.json({
      user: req.authUser,
      message: "This requires a valid Bearer token",
    });
  },
);

// GET /logout — Revoke tokens and clear session
app.get("/logout", async (req, res) => {
  const tokens = req.session?.tokens;
  if (tokens?.refresh_token) {
    try {
      // Optional: revoke on server side
      // await authflow.revokeToken(tokens.refresh_token);
    } catch (err) {
      console.error("Token revocation failed:", err);
    }
  }
  req.session?.destroy(() => {
    res.redirect("/");
  });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});

Quick start — Next.js App Router

// app/api/me/route.ts
import { AuthFlowNodeClient, getServerUser } from "@meet_patel_03/authflow-node";

const authflow = new AuthFlowNodeClient({ ... });

export async function GET(req: Request) {
  const user = await getServerUser(req, authflow);
  if (!user) return Response.json({ error: "unauthorized" }, { status: 401 });
  return Response.json({ user });
}

// app/login/route.ts
import { cookies, redirect } from "next/headers";
import { generateCodeVerifier, generateCodeChallenge, generateState, AuthFlowNodeClient } from "@meet_patel_03/authflow-node";

const authflow = new AuthFlowNodeClient({
  domain: process.env.AUTHFLOW_DOMAIN!,
  clientId: process.env.AUTHFLOW_CLIENT_ID!,
  clientSecret: process.env.AUTHFLOW_CLIENT_SECRET!,
  redirectUri: process.env.AUTHFLOW_REDIRECT_URI!,
});

export async function GET() {
  const verifier  = generateCodeVerifier();
  const challenge = generateCodeChallenge(verifier);
  const state     = generateState();
  cookies().set("pkce_verifier", verifier, { httpOnly: true, secure: true, sameSite: "lax" });
  cookies().set("pkce_state",    state,    { httpOnly: true, secure: true, sameSite: "lax" });
  redirect(authflow.buildAuthorizeUrl({ state, codeChallenge: challenge, codeChallengeMethod: "S256" }));
}

// app/callback/route.ts
import { cookies, redirect } from "next/headers";
import { AuthFlowNodeClient, AuthFlowNodeError } from "@meet_patel_03/authflow-node";

const authflow = new AuthFlowNodeClient({
  domain: process.env.AUTHFLOW_DOMAIN!,
  clientId: process.env.AUTHFLOW_CLIENT_ID!,
  clientSecret: process.env.AUTHFLOW_CLIENT_SECRET!,
  redirectUri: process.env.AUTHFLOW_REDIRECT_URI!,
});

export async function GET(req: Request) {
  try {
    const { searchParams } = new URL(req.url);
    const code = searchParams.get("code");
    const state = searchParams.get("state");
    const error = searchParams.get("error");

    // Check for OAuth2 errors
    if (error) {
      throw new AuthFlowNodeError(
        error,
        searchParams.get("error_description") || undefined
      );
    }

    // Validate state (CSRF protection)
    const storedState = cookies().get("pkce_state")?.value;
    const storedVerifier = cookies().get("pkce_verifier")?.value;

    if (!state || !storedState || state !== storedState) {
      return new Response("State mismatch", { status: 400 });
    }

    if (!code || !storedVerifier) {
      return new Response("Missing code or verifier", { status: 400 });
    }

    // Exchange code for tokens
    const tokens = await authflow.exchangeCode(code, storedVerifier);

    // Store tokens in secure httpOnly cookie
    cookies().set("authflow_tokens", JSON.stringify(tokens), {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "lax",
      maxAge: tokens.expires_in,
    });

    // Clean up PKCE cookies
    cookies().delete("pkce_verifier");
    cookies().delete("pkce_state");

    redirect("/dashboard");
  } catch (err) {
    console.error("Callback error:", err);
    return new Response(
      JSON.stringify({
        error: "callback_failed",
        message: err instanceof Error ? err.message : "Unknown error",
      }),
      { status: 400, headers: { "Content-Type": "application/json" } }
    );
  }
}

Configuration

| Option | Required | Description | | -------------- | -------- | ----------------------------------------------------------------------------------------------- | | domain | Yes | Your AuthFlow backend URL (e.g. https://auth.yourcompany.com) | | clientId | Yes | Application client_id from the AuthFlow dashboard | | clientSecret | Yes | Application client_secret from the dashboard. Keep in env vars — never expose to frontend | | redirectUri | Yes | OAuth2 redirect URI. Must match an entry in your Application's Allowed Callback URLs | | scope | No | Space-separated OAuth2 scopes (defaults to openid profile email) |

API

new AuthFlowNodeClient(config)

Initializes the SDK. Throws an error if required config values are missing.

const authflow = new AuthFlowNodeClient({
  domain: process.env.AUTHFLOW_DOMAIN!,
  clientId: process.env.AUTHFLOW_CLIENT_ID!,
  clientSecret: process.env.AUTHFLOW_CLIENT_SECRET!,
  redirectUri: process.env.AUTHFLOW_REDIRECT_URI!,
  scope: "openid profile email", // optional
});

buildAuthorizeUrl(params)

Builds the AuthFlow /authorize URL. Always generate and store a new code_verifier and state before calling this.

Parameters:

  • state (required) — CSRF token. Store in session/cookie before redirecting.
  • codeChallenge (required) — PKCE challenge generated from generateCodeChallenge().
  • codeChallengeMethod (required) — Always "S256".
  • screen_hint (optional) — "signup" to show registration page, or omit for login.

Returns: Full authorization URL string.

const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);
const state = generateState();

// Store verifier and state in session/cookie
sessionStorage.pkce = { verifier, state };

// Build and redirect
const authUrl = authflow.buildAuthorizeUrl({
  state,
  codeChallenge: challenge,
  codeChallengeMethod: "S256",
  screen_hint: "login", // optional
});
res.redirect(authUrl);

exchangeCode(code, codeVerifier)

Exchanges an authorization code for tokens. Must verify the state parameter matches before calling.

Parameters:

  • code — Authorization code from the OAuth2 callback.
  • codeVerifier — The original PKCE verifier (from generateCodeVerifier()).

Returns: TokenSet containing access_token, refresh_token, id_token, and metadata.

Throws: AuthFlowNodeError if the exchange fails (invalid code, expired code, etc.).

try {
  const tokens = await authflow.exchangeCode(
    req.query.code as string,
    req.session.pkce.verifier,
  );
  req.session.tokens = tokens; // Store in session
  res.redirect("/dashboard");
} catch (err) {
  if (err instanceof AuthFlowNodeError) {
    console.error("Token exchange failed:", err.error, err.errorDescription);
  }
}

refreshTokens(refreshToken)

Silently refreshes an access token using the refresh token. Use this in middleware when the stored access token is expired.

Parameters:

  • refreshToken — The stored refresh token.

Returns: New TokenSet. The refresh token is preserved unless the server issued a new one.

Throws: AuthFlowNodeError if the refresh fails (token revoked, user logged out elsewhere, etc.).

if (authflow.isTokenExpired(req.session.tokens)) {
  try {
    req.session.tokens = await authflow.refreshTokens(
      req.session.tokens.refresh_token,
    );
  } catch (err) {
    // Refresh failed — user needs to re-authenticate
    return res.redirect("/login");
  }
}

verifyIdToken(idToken, publicKeyOrSecret, options?)

Verifies the JWT signature of an id_token and returns the decoded user claims. Use this for Bearer token validation in your API endpoints.

Parameters:

  • idToken — The JWT id_token string.
  • publicKeyOrSecret — Public key (RS256) or shared secret (HS256). Use process.env.OIDC_PUBLIC_KEY for production.
  • options.algorithms (optional) — JWT algorithms to accept (defaults to ["RS256", "HS256"]).

Returns: AuthFlowUser with properties: sub, email, name, email_verified, and custom claims.

Throws: AuthFlowNodeError if verification fails (invalid signature, expired token, wrong audience, etc.).

try {
  const user = authflow.verifyIdToken(
    bearerToken,
    process.env.OIDC_PUBLIC_KEY!,
  );
  console.log("Authenticated user:", user.email);
} catch (err) {
  console.error(
    "Token invalid:",
    err instanceof AuthFlowNodeError ? err.error : err,
  );
  return res.status(401).json({ error: "unauthorized" });
}

decodeIdToken(idToken)

Decodes an id_token without verifying the signature. Use only when you trust the token source (e.g., it was just received from your own /oauth/token endpoint).

Parameters:

  • idToken — The JWT string.

Returns: AuthFlowUser | null — null if decoding fails.

const user = authflow.decodeIdToken(tokens.id_token);
if (user) {
  console.log("User email:", user.email);
} else {
  console.error("Failed to decode id_token");
}

isTokenExpired(tokens, bufferSeconds?)

Checks if the access token is expired or within the buffer time.

Parameters:

  • tokens — The TokenSet to check.
  • bufferSeconds (optional) — Seconds before actual expiry to consider "expired" (default: 60). Use 0 to check actual expiry.

Returns: booleantrue if expired or expiring soon.

if (authflow.isTokenExpired(req.session.tokens)) {
  // Token is expired or about to expire — consider refreshing
}

if (authflow.isTokenExpired(req.session.tokens, 0)) {
  // Token is already expired (no buffer)
}

PKCE Helpers

These functions use Node.js crypto for secure randomness:

generateCodeVerifier()

Generates a cryptographically secure PKCE code verifier.

Returns: 43-128 character base64url string.

const verifier = generateCodeVerifier();
// Example: "E9Mzc...DPxQo9aw"

generateCodeChallenge(verifier)

Creates an S256 (SHA-256) code challenge from a verifier.

Parameters:

  • verifier — Output from generateCodeVerifier().

Returns: base64url-encoded SHA-256 hash.

const verifier = generateCodeVerifier();
const challenge = generateCodeChallenge(verifier);
// Store verifier, use challenge in authorization request

generateState()

Generates a random CSRF state token.

Returns: 16-character base64url string.

const state = generateState();
// Example: "kX92nL...Qr8pZQ"

Middleware

createExpressMiddleware(client, options?)

Express middleware that validates Bearer tokens on API endpoints.

Parameters:

  • clientAuthFlowNodeClient instance.
  • options.publicKeyOrSecret (optional) — JWT verification key. Defaults to process.env.OIDC_PUBLIC_KEY or process.env.JWT_ACCESS_SECRET.
  • options.credentialsRequired (optional) — Reject requests without Bearer token (default: true). Set to false to allow unauthenticated access but populate req.authUser when a token is present.

Effects:

  • Populates req.authUser with decoded user claims if authentication succeeds.
  • Returns 401 with error JSON if token is missing or invalid.
// Require Bearer token on all /api routes
app.use("/api", createExpressMiddleware(authflow));

app.get("/api/me", (req, res) => {
  // req.authUser is guaranteed to exist here
  res.json({ user: req.authUser });
});

// Optional authentication on /public routes
app.use(
  "/public",
  createExpressMiddleware(authflow, { credentialsRequired: false }),
);
app.get("/public/profile", (req, res) => {
  if (req.authUser) {
    res.json({ message: "Logged in", user: req.authUser });
  } else {
    res.json({ message: "Anonymous" });
  }
});

createSessionRefreshMiddleware(client)

Express middleware that automatically refreshes expired tokens stored in req.session.tokens.

Parameters:

  • clientAuthFlowNodeClient instance.

Effects:

  • If access token is expired or expiring within 60 seconds, silently refreshes it.
  • Updates req.session.tokens with new tokens.
  • Calls next() to continue (does not send a response).
app.use(createSessionRefreshMiddleware(authflow));

app.get("/protected", (req, res) => {
  // Tokens are guaranteed fresh at this point
  const tokens = req.session.tokens;
  res.json({ accessToken: tokens.access_token });
});

requireAuth(loginUrl?)

Express middleware that redirects unauthenticated users to a login page.

Parameters:

  • loginUrl (optional) — URL to redirect to if req.session.tokens is missing (default: /login).

Effects:

  • If tokens are not stored in session, redirects to the login URL.
  • Otherwise, calls next() to continue.
app.get("/dashboard", requireAuth("/login"), (req, res) => {
  res.json({ message: "Welcome", tokens: req.session.tokens });
});

getServerUser(request, client, options?)

Next.js helper to extract and verify the authenticated user from a Request.

Parameters:

  • request — Next.js Request object.
  • clientAuthFlowNodeClient instance.
  • options.cookieName (optional) — Cookie name for tokens (default: authflow_tokens).
  • options.publicKeyOrSecret (optional) — JWT verification key.

Returns: AuthFlowUser | null — null if no token is found or verification fails.

import { getServerUser } from "@meet_patel_03/authflow-node";

export async function GET(req: Request) {
  const user = await getServerUser(req, authflow);
  if (!user) {
    return Response.json({ error: "unauthorized" }, { status: 401 });
  }
  return Response.json({ user });
}

Error Handling

The SDK exports AuthFlowNodeError for OAuth2 and authentication failures:

import { AuthFlowNodeError } from "@meet_patel_03/authflow-node";

try {
  await authflow.exchangeCode(code, verifier);
} catch (err) {
  if (err instanceof AuthFlowNodeError) {
    console.error("Error:", err.error);
    console.error("Description:", err.errorDescription);
    // Common errors:
    // - "invalid_grant" — Code expired, already used, or invalid
    // - "invalid_client" — clientId or clientSecret incorrect
    // - "refresh_failed" — Refresh token revoked or expired
  } else {
    console.error("Network or unknown error:", err);
  }
}

Types

The SDK exports the following TypeScript types:

  • NodeClientConfig — Constructor configuration object
  • AuthFlowUser — Decoded user claims from id_token (sub, email, name, email_verified, custom fields)
  • TokenSet — OAuth2 tokens (access_token, refresh_token, id_token, expires_in, expires_at, scope)
  • AuthorizeUrlParams — Parameters for buildAuthorizeUrl()
  • AuthFlowNodeError — OAuth2 errors with error and errorDescription properties

Common Patterns

Express: Protected API with auto-refresh

app.use("/api", createSessionRefreshMiddleware(authflow));
app.use("/api", createExpressMiddleware(authflow));
app.get("/api/profile", (req, res) => res.json(req.authUser));

Next.js: Server component with auth

import { cookies } from "next/headers";
import { authflow } from "@/lib/authflow";

export default async function ProfilePage() {
  const c = await cookies();
  const tokensJson = c.get("authflow_tokens")?.value;
  const tokens = tokensJson ? JSON.parse(tokensJson) : null;

  if (!tokens) {
    redirect("/login");
  }

  const user = authflow.decodeIdToken(tokens.id_token);
  return <div>Welcome, {user?.email}</div>;
}