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

@smarthivelabs-devs/auth-server

v1.1.1

Published

SmartHive Auth middleware for Express and Next.js App Router — JWT verification via JWKS and social auth proxy router

Readme

@smarthivelabs-devs/auth-server

SmartHive Auth server-side middleware for Express and Next.js. Verifies JWTs using JWKS — no shared secrets required.


Installation

npm install @smarthivelabs-devs/auth-server
# or
pnpm add @smarthivelabs-devs/auth-server
# or
yarn add @smarthivelabs-devs/auth-server

For Express projects, also install the peer dependency:

npm install express

express is optional — Next.js users do not need it.


How It Works

The server SDK verifies Authorization: Bearer <token> JWTs by fetching your SmartHive Auth project's public JSON Web Key Set (JWKS) at:

https://<your-issuer>/api/auth/jwks

No secrets are shared between your server and SmartHive Auth — only public keys are used for verification. Keys are fetched automatically and cached by jose.


Configuration

interface ServerAuthConfig {
  /** Your SmartHive Auth issuer URL — MUST be https:// */
  issuer: string;

  /** Optional: restrict accepted tokens to a specific audience */
  audience?: string;

  /** Optional: restrict accepted tokens to a specific project */
  projectId?: string;
}

The verified token is decoded into an AuthContext:

interface AuthContext {
  userId: string;        // token subject (sub claim)
  projectId?: string;    // project_id claim from the token
  email?: string;        // email claim from the token
  raw: JWTPayload;       // full decoded JWT payload
}

Express Middleware

requireAuth(config)

An Express middleware that verifies the Authorization: Bearer <token> header. Attaches the decoded AuthContext to req.auth on success. Returns 401 on missing or invalid tokens.

import express from "express";
import { requireAuth } from "@smarthivelabs-devs/auth-server";

const app = express();

const auth = requireAuth({
  issuer: process.env.AUTH_ISSUER!,       // e.g. "https://auth.myapp.com"
  projectId: process.env.AUTH_PROJECT_ID, // optional — restricts to your project
});

// Apply to a single route
app.get("/api/profile", auth, (req, res) => {
  res.json({ userId: req.auth!.userId, email: req.auth!.email });
});

// Apply to an entire router
const apiRouter = express.Router();
apiRouter.use(auth);

apiRouter.get("/me", (req, res) => {
  res.json(req.auth);
});

apiRouter.post("/posts", (req, res) => {
  const userId = req.auth!.userId;
  // create post for userId...
  res.json({ ok: true });
});

app.use("/api", apiRouter);
app.listen(3000);

TypeScript: Typed Request

The req.auth field is added by the middleware. Use SmartHiveAuthRequest for type-safe access:

import type { SmartHiveAuthRequest } from "@smarthivelabs-devs/auth-server";
import type { Response, NextFunction } from "express";

function myHandler(req: SmartHiveAuthRequest, res: Response) {
  const { userId, email, projectId, raw } = req.auth!;
  res.json({ userId, email });
}

Full Express App Example

import express from "express";
import { requireAuth, type SmartHiveAuthRequest } from "@smarthivelabs-devs/auth-server";

const app = express();
app.use(express.json());

const authMiddleware = requireAuth({
  issuer: process.env.AUTH_ISSUER!,
  projectId: process.env.AUTH_PROJECT_ID,
});

// Public route
app.get("/health", (_req, res) => res.json({ status: "ok" }));

// Protected route
app.get("/api/me", authMiddleware, (req: SmartHiveAuthRequest, res) => {
  res.json({
    userId: req.auth!.userId,
    email: req.auth!.email,
  });
});

app.put("/api/profile", authMiddleware, express.json(), async (req: SmartHiveAuthRequest, res) => {
  const userId = req.auth!.userId;
  const { displayName } = req.body;
  // update profile in DB...
  res.json({ ok: true });
});

// Error responses from requireAuth:
// 401 { error: "unauthorized" }  — no token
// 401 { error: "invalid_token" } — bad/expired token

app.listen(3000, () => console.log("Server running on :3000"));

Next.js App Router

createNextAuthMiddleware(config)

Returns an async function you can call inside your Next.js middleware.ts (Edge runtime) or route handlers. Accepts a Next.js-style request object and returns { auth, error }.

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { createNextAuthMiddleware } from "@smarthivelabs-devs/auth-server";

const verifyAuth = createNextAuthMiddleware({
  issuer: process.env.AUTH_ISSUER!,
  projectId: process.env.AUTH_PROJECT_ID,
});

export async function middleware(request: NextRequest) {
  const { auth, error } = await verifyAuth(request);

  if (error) {
    // Redirect browser requests to login, reject API requests with 401
    if (request.nextUrl.pathname.startsWith("/api/")) {
      return NextResponse.json({ error: "unauthorized" }, { status: 401 });
    }
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  // auth.userId is available here
  // Optionally forward user info to route handlers via headers
  const res = NextResponse.next();
  res.headers.set("x-user-id", auth!.userId);
  return res;
}

export const config = {
  matcher: ["/dashboard/:path*", "/api/protected/:path*"],
};

In Route Handlers (App Router)

// app/api/profile/route.ts
import { createNextAuthMiddleware } from "@smarthivelabs-devs/auth-server";
import { NextRequest, NextResponse } from "next/server";

const verifyAuth = createNextAuthMiddleware({
  issuer: process.env.AUTH_ISSUER!,
});

export async function GET(request: NextRequest) {
  const { auth, error } = await verifyAuth(request);

  if (error) {
    return NextResponse.json({ error: "unauthorized" }, { status: 401 });
  }

  return NextResponse.json({
    userId: auth!.userId,
    email: auth!.email,
  });
}

Next.js Pages Router (API Routes)

// pages/api/profile.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { verifyAccessToken } from "@smarthivelabs-devs/auth-server";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const token = req.headers.authorization?.slice("Bearer ".length);
  if (!token) return res.status(401).json({ error: "unauthorized" });

  try {
    const auth = await verifyAccessToken(token, {
      issuer: process.env.AUTH_ISSUER!,
    });
    res.json({ userId: auth.userId, email: auth.email });
  } catch {
    res.status(401).json({ error: "invalid_token" });
  }
}

Standalone: verifyAccessToken

Use this directly when you need full control over the verification flow.

import { verifyAccessToken } from "@smarthivelabs-devs/auth-server";

const auth = await verifyAccessToken(token, {
  issuer: "https://auth.myapp.com",
  audience: "https://api.myapp.com",   // optional
  projectId: "proj_abc123",             // optional
});

console.log(auth.userId);    // e.g. "user_abc123"
console.log(auth.email);     // e.g. "[email protected]"
console.log(auth.raw);       // full JWT payload object

Throws Error if:

  • issuer does not start with https://
  • JWKS fetch fails
  • Token signature is invalid
  • Token is expired
  • audience doesn't match (if provided)
  • projectId doesn't match the token's project_id claim (if provided)

decodeClaims(token)

Decodes a JWT without verifying the signature. Use only for reading non-sensitive claims before verification (e.g. extracting the projectId to look up the correct issuer).

import { decodeClaims } from "@smarthivelabs-devs/auth-server";

const claims = decodeClaims(token);
console.log(claims.sub);           // user ID
console.log(claims.project_id);    // project ID
console.log(claims.exp);           // expiry timestamp

Never use decodeClaims alone for authorization decisions — the signature is not verified.


Multi-tenant Setup

If you serve multiple projects from one server, use decodeClaims to extract the project ID, then look up the correct issuer:

import { decodeClaims, verifyAccessToken } from "@smarthivelabs-devs/auth-server";

const projectIssuers: Record<string, string> = {
  proj_abc: "https://auth.tenant-a.com",
  proj_xyz: "https://auth.tenant-b.com",
};

async function verifyMultiTenantToken(token: string) {
  const claims = decodeClaims(token);
  const projectId = String(claims.project_id ?? "");
  const issuer = projectIssuers[projectId];

  if (!issuer) throw new Error("Unknown project");

  return verifyAccessToken(token, { issuer, projectId });
}

Social Auth Proxy (White-label OAuth Domain)

By default, when users tap "Sign in with Google" the iOS prompt shows SmartHive's domain and the Google consent screen says "to continue to smarthivelabs.dev". createSocialProxyRouter fixes this by routing OAuth through your own backend — Google and Apple only ever see your domain.

How It Works

App → your-api.com/api/auth/social/google?redirect_uri=myapp://callback
    → (your backend) redirects to SmartHive with proxy_callback=your-api.com/...
    → (SmartHive) sends redirect_uri=your-api.com/... to Google
    → Google consent screen shows "to continue to your-app.com" ✓
    → iOS shows "wants to use your-api.com" ✓
    → Google → your-api.com/api/auth/social/google/callback?code=...
    → (your backend) forwards to SmartHive → SmartHive issues tokens
    → myapp://callback?access_token=...  (app gets token) ✓

Setup

Step 1 — Mount the proxy router in your Express backend:

import express from "express";
import { createSocialProxyRouter } from "@smarthivelabs-devs/auth-server";

const app = express();

app.use("/api/auth/social", await createSocialProxyRouter({
  smarthiveBaseUrl: process.env.SMARTHIVE_AUTH_BASE_URL!,  // https://authcore.smarthivelabs.dev
  environment: process.env.SMARTHIVE_AUTH_ENVIRONMENT!,    // prod | staging | dev
  projectId: process.env.SMARTHIVE_PROJECT_ID!,
  appBaseUrl: process.env.APP_BASE_URL!,                   // https://api.yourapp.com
}));

Step 2 — Add env vars:

SMARTHIVE_AUTH_BASE_URL=https://authcore.smarthivelabs.dev
SMARTHIVE_AUTH_ENVIRONMENT=prod
SMARTHIVE_PROJECT_ID=your-project-id
APP_BASE_URL=https://api.yourapp.com

Step 3 — Register your backend callback URL with Google/Apple:

  • Google Cloud Console → OAuth 2.0 → Authorized redirect URIs: https://api.yourapp.com/api/auth/social/google/callback
  • Apple Developer → Service ID → Return URLs: https://api.yourapp.com/api/auth/social/apple/callback

Step 4 — Point your SDK at the proxy:

// Expo / React Native
<SmartHiveAuthProvider
  publishableKey="pk_..."
  projectId="proj_..."
  baseUrl="https://authcore.smarthivelabs.dev"
  redirectUri="myapp://auth/callback"
  socialProxyUrl="https://api.yourapp.com"   // ← add this
>

// React web
<SmartHiveAuthProvider
  publishableKey="pk_..."
  projectId="proj_..."
  baseUrl="https://authcore.smarthivelabs.dev"
  socialProxyUrl="https://api.yourapp.com"   // ← add this
>

Config Reference

interface SocialProxyConfig {
  /** SmartHive Auth service base URL, e.g. "https://authcore.smarthivelabs.dev" */
  smarthiveBaseUrl: string;
  /** Environment: "dev" | "staging" | "prod" */
  environment: string;
  /** Your SmartHive project ID */
  projectId: string;
  /**
   * Your backend's public base URL, e.g. "https://api.myapp.com".
   * This is the domain that will appear on Google/Apple consent screens.
   */
  appBaseUrl: string;
}

The router creates two routes automatically:

  • GET /:provider — initiates the OAuth flow, redirects to SmartHive with proxy_callback set
  • GET /:provider/callback — receives the provider redirect, forwards to SmartHive for token exchange

Fastify Integration

import Fastify from "fastify";
import { verifyAccessToken } from "@smarthivelabs-devs/auth-server";

const app = Fastify();

app.addHook("onRequest", async (request, reply) => {
  const authHeader = request.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    reply.code(401).send({ error: "unauthorized" });
    return;
  }
  try {
    (request as any).auth = await verifyAccessToken(authHeader.slice(7), {
      issuer: process.env.AUTH_ISSUER!,
    });
  } catch {
    reply.code(401).send({ error: "invalid_token" });
  }
});

app.get("/api/me", async (request) => {
  return (request as any).auth;
});

app.listen({ port: 3000 });

tRPC Integration

import { initTRPC, TRPCError } from "@trpc/server";
import { verifyAccessToken } from "@smarthivelabs-devs/auth-server";
import type { AuthContext } from "@smarthivelabs-devs/auth-server";

type Context = { auth: AuthContext | null };

export const t = initTRPC.context<Context>().create();

// Middleware that requires auth
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.auth) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({ ctx: { auth: ctx.auth } });
});

// In your context factory (createContext)
export async function createContext({ req }: { req: Request }): Promise<Context> {
  const token = req.headers.get("authorization")?.slice("Bearer ".length);
  if (!token) return { auth: null };
  try {
    const auth = await verifyAccessToken(token, {
      issuer: process.env.AUTH_ISSUER!,
    });
    return { auth };
  } catch {
    return { auth: null };
  }
}

Environment Variables

# .env
AUTH_ISSUER=https://auth.myapp.com
AUTH_PROJECT_ID=proj_abc123
# Optional — if your tokens have an audience claim
AUTH_AUDIENCE=https://api.myapp.com

Security Notes

  • HTTPS required: The issuer must start with https://. The SDK enforces this and will throw if you pass an http:// URL.
  • No shared secrets: Verification uses only public JWKS keys — your server never receives or stores any private keys or client secrets.
  • Key rotation: JWKS keys are fetched and cached by jose. Key rotation on the SmartHive side is handled transparently.
  • Audience validation: Pass audience to restrict tokens to your specific API service, preventing tokens issued for other services from being accepted.
  • Project isolation: Pass projectId to reject tokens from other SmartHive projects.

TypeScript Types

import type {
  ServerAuthConfig,
  AuthContext,
  SmartHiveAuthRequest,   // extends Express Request
  NextRequest,            // minimal Next.js-compatible request type
} from "@smarthivelabs-devs/auth-server";

Related Packages

| Package | Use case | |---|---| | @smarthivelabs-devs/auth-sdk | Core SDK — framework-agnostic client | | @smarthivelabs-devs/auth-react | React web apps — provider and hooks | | @smarthivelabs-devs/auth-expo | React Native / Expo apps |


License

MIT © SmartHive Labs