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.3.1

Published

SmartHive Auth server SDK — JWT verification via JWKS, server-side token exchange, admin client, 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.

Environments

SmartHive Auth supports two environments: dev and prod. The environment is encoded in the publishable key prefix (pk_dev_* or pk_prod_*) — there is no environment segment in the URL path. All client requests go to the same base URL (/api/auth/*) and the publishable key header (x-smarthive-publishable-key) determines which environment and project to use.


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.SMARTHIVE_AUTH_ISSUER!,       // e.g. "https://auth.myapp.com"
  projectId: process.env.SMARTHIVE_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.SMARTHIVE_AUTH_ISSUER!,
  projectId: process.env.SMARTHIVE_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.SMARTHIVE_AUTH_ISSUER!,
  projectId: process.env.SMARTHIVE_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.SMARTHIVE_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.SMARTHIVE_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.


Server-side Token Exchange (sk_* client secret)

When you create a SmartHive Auth project you receive a confidential OAuth client — a shc_* client ID and a sk_* client secret. Use these on your backend to exchange an authorization code for tokens without PKCE. The sk_* secret must never be sent to the browser.

This is the right approach for Next.js API routes, Express callbacks, and any server that handles the OAuth redirect.

exchangeCodeForTokens(code, redirectUri, config)

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

// app/api/auth/callback/route.ts (Next.js App Router)
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const code = searchParams.get("code");
  if (!code) return new Response("Missing code", { status: 400 });

  const tokens = await exchangeCodeForTokens(code, "https://myapp.com/api/auth/callback", {
    clientId: process.env.SMARTHIVE_AUTH_CLIENT_ID!,      // shc_*
    clientSecret: process.env.SMARTHIVE_AUTH_CLIENT_SECRET!, // sk_*
    issuer: process.env.SMARTHIVE_AUTH_ISSUER!,
    environment: process.env.SMARTHIVE_AUTH_ENVIRONMENT!,   // "dev" | "prod"
  });

  // tokens.accessToken, tokens.refreshToken, tokens.expiresAt
  return Response.json({ ok: true });
}

refreshAccessToken(refreshToken, config)

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

const tokens = await refreshAccessToken(storedRefreshToken, {
  clientId: process.env.SMARTHIVE_AUTH_CLIENT_ID!,
  clientSecret: process.env.SMARTHIVE_AUTH_CLIENT_SECRET!,
  issuer: process.env.SMARTHIVE_AUTH_ISSUER!,
  environment: "prod",  // "dev" | "prod"
});

ServerTokenExchangeConfig

interface ServerTokenExchangeConfig {
  /** The shc_* OAuth client ID issued at project creation */
  clientId: string;
  /** The sk_* client secret — never expose to the browser */
  clientSecret: string;
  /** Base URL of your SmartHive Auth service */
  issuer: string;
  /** "dev" | "prod" */
  environment: string;
}

interface TokenResponse {
  accessToken: string;
  refreshToken?: string;
  expiresAt?: number;   // Unix timestamp in seconds
  tokenType: string;    // "bearer"
}

Environment variables

SMARTHIVE_AUTH_CLIENT_ID=shc_prod_abc123          # from project dashboard → Credentials
SMARTHIVE_AUTH_CLIENT_SECRET=sk_prod_abc123       # from project dashboard → Credentials (keep secret)
SMARTHIVE_AUTH_ISSUER=https://auth.myapp.com
SMARTHIVE_AUTH_ENVIRONMENT=prod                   # "dev" | "prod"

Admin Client (shai_* secret key)

When you create a SmartHive Auth project you also receive a shai_* internal API key. This key unlocks the /internal/* management API — listing users, revoking sessions, managing orgs, reading audit logs.

Use createAdminClient() on your server (never in the browser).

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

const admin = createAdminClient({
  secretKey: process.env.SMARTHIVE_AUTH_ADMIN_KEY!, // shai_*
  baseUrl: process.env.SMARTHIVE_AUTH_ISSUER!,
});

// List users
const { users } = await admin.users.list();

// Get a single user
const { user } = await admin.users.get("user_abc123");

// Update a user (ban them)
await admin.users.update("user_abc123", { banReason: "TOS violation", banExpires: null });

// Delete a user
await admin.users.delete("user_abc123");

// List active sessions for a user
const { sessions } = await admin.users.listSessions("user_abc123");

// Revoke a specific session
await admin.users.revokeSession("user_abc123", "session_xyz");

// List all sessions (with filters)
const active = await admin.sessions.list({ status: "active", limit: 100 });

// Revoke a session by ID directly
await admin.sessions.revoke("session_xyz");

// Org management
const { organizations } = await admin.orgs.list();
await admin.orgs.addMember("org_123", { actorId: "user_abc", role: "developer" });
await admin.orgs.removeMember("org_123", "user_abc");

// Audit log
const { entries } = await admin.audit.list({ action: "session.revoked", limit: 50 });

AdminUser type

interface AdminUser {
  id: string;
  email: string;
  name: string | null;
  image: string | null;
  phoneNumber: string | null;
  phoneNumberVerified: string | null;
  emailVerified: boolean;
  createdAt: string;
  updatedAt: string;
  banReason: string | null;
  banExpires: string | null;
}

Environment variable

SMARTHIVE_AUTH_ADMIN_KEY=shai_prod_abc123    # from project dashboard → Credentials

The write operations (update, delete, revokeSession, revoke, addMember, removeMember) require the shai_* key to have the corresponding *:write scope. Read-only keys can only call list and get methods.


Expo / React Native Integration

The requireAuth middleware works identically for requests that come from the @smarthivelabs-devs/auth-expo SDK — no extra setup needed. The Expo SDK automatically sends Authorization: Bearer <token> on every request made via authFetch or client.fetch, and transparently refreshes expired tokens before the next request fires.

Full flow (sign-in → backend call):

Expo App                              Developer Backend
────────────────────────────────────────────────────────
1. signIn.email() / signIn.social()     ← SmartHive handles, not your server
   SecureStore ← tokens saved

2. authFetch("https://api.myapp.com/profile")
   Authorization: Bearer <access_token>  ──►  requireAuth()
                                              verifies JWT via JWKS
                                              ◄── req.auth.userId
                                              your handler runs
                                         ◄── JSON response

Sign-up, OTP delivery, token refresh — all go directly to SmartHive Auth and do not touch your backend. The only thing your server receives is authenticated API calls with a verified Bearer token.

// Your backend — nothing different from a web client
app.get("/api/profile", requireAuth({ issuer: process.env.SMARTHIVE_AUTH_ISSUER! }), (req, res) => {
  res.json({ userId: req.auth!.userId, email: req.auth!.email });
});

If you need the full user record (name, phone, etc.), combine requireAuth with createAdminClient:

const admin = createAdminClient({
  secretKey: process.env.SMARTHIVE_AUTH_ADMIN_KEY!,
  baseUrl: process.env.SMARTHIVE_AUTH_ISSUER!,
});

app.get("/api/profile", requireAuth({ issuer: process.env.SMARTHIVE_AUTH_ISSUER! }), async (req, res) => {
  const { user } = await admin.users.get(req.auth!.userId);
  res.json(user);
});

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!,    // "dev" | "prod"
  projectId: process.env.SMARTHIVE_AUTH_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   # "dev" | "prod"
SMARTHIVE_AUTH_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" | "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.SMARTHIVE_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.SMARTHIVE_AUTH_ISSUER!,
    });
    return { auth };
  } catch {
    return { auth: null };
  }
}

Environment Variables

# .env
SMARTHIVE_AUTH_ISSUER=https://auth.myapp.com
SMARTHIVE_AUTH_PROJECT_ID=proj_abc123
# Optional — if your tokens have an audience claim
SMARTHIVE_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.
  • JWT verification uses no shared secrets: verifyAccessToken / requireAuth / createNextAuthMiddleware fetch only public JWKS keys — no private key or client secret is involved.
  • sk_* is a real secret: exchangeCodeForTokens and refreshAccessToken use the sk_* client secret. Store it in an environment variable and never expose it in client-side code, logs, or public repos.
  • shai_* is a real secret: The admin client sends shai_* as an x-internal-api-key header. Treat it like a root credential — store it server-side only.
  • 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
  ServerTokenExchangeConfig,   // config for exchangeCodeForTokens / refreshAccessToken
  TokenResponse,               // { accessToken, refreshToken?, expiresAt?, tokenType }
  AdminClientConfig,           // config for createAdminClient
  SmartHiveAdminClient,        // return type of createAdminClient
  AdminUser,                   // user shape from admin.users.*
} 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