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

internal-dw-api-auth

v0.1.0

Published

Reusable server-to-server API-key authentication and explicit department context

Readme

internal-dw-api-auth

Reusable server-to-server API-key authentication for EIS, LMS, Policy Management and a common GraphQL API. This library supplies Express-compatible middleware, credential helpers and explicit department context. Applications own their server, database lookup, configuration and GraphQL context.

Compatibility and installation

Requires Node.js 18.0.0 or newer, based on the oldest consuming application's runtime. CommonJS JavaScript and TypeScript declarations are included. CommonJS require() and Node ESM named imports both use the same compiled entry point. Express is supplied by the consuming application; no Express runtime is bundled. The library does not use global fetch or global request state.

npm install internal-dw-api-auth
import { generateApiKey } from 'internal-dw-api-auth';
// CommonJS: const { generateApiKey } = require('internal-dw-api-auth');

Generate and store credentials

const generated = generateApiKey({ prefix: 'example' });
const { keyId, secretHash } = generated;
// Persist keyId and secretHash using your application's storage layer.
// Deliver generated.apiKey once through your secure provisioning workflow.

The format is keyId.secret. The key ID contains the prefix and 16 random bytes encoded as hex; it is a public lookup identifier. Prefixes must contain 1–64 ASCII letters, digits, underscores or hyphens. Enforce uniqueness in your storage layer. The secret contains 32 cryptographically secure random bytes encoded as hex. secretHash is SHA-256 of only the secret. Store the hash on the receiving API; the calling application must securely retain the raw key to authenticate. Never log the generated object, raw key, secret, hash or authorization headers.

parseApiKey(apiKey) splits at the first dot and rejects missing or empty parts. hashApiKeySecret(secret) returns a hex SHA-256 digest. verifyApiKeySecret(secret, storedSecretHash) uses a constant-time buffer comparison and returns false for malformed hashes.

Authentication and IP filtering

The following configures an existing application and router; the package does not create or start a server. keyRepository is an application-owned adapter, and allowedNetworks is parsed by the application from configuration.

import {
  createApiKeyAuthenticationMiddleware,
  createIpWhitelistMiddleware,
  type FindApiKey,
} from 'internal-dw-api-auth';

const findApiKey: FindApiKey = async keyId => {
  const row = await keyRepository.findByKeyId(keyId);
  if (!row) return null;
  return {
    keyId: row.keyId,
    secretHash: row.secretHash,
    active: row.active,
    expiresAt: row.expiresAt, // Date or null; convert serialized dates in your adapter.
    revokedAt: row.revokedAt, // Date or null.
    client: { id: row.client.id, code: row.client.code, active: row.client.active },
  };
};

internalRouter.use(createIpWhitelistMiddleware({ allowedNetworks }));
internalRouter.use(createApiKeyAuthenticationMiddleware({ findApiKey }));

Requests send Authorization: ApiKey keyId.secret. Missing/malformed credentials, unknown keys, wrong secrets, inactive keys or clients, revoked keys and expired keys all receive 401 { "error": "Unauthorized" }. Expiration at the current instant is rejected. Authentication attaches req.internalRequestContext.client. A failed database lookup forwards a sanitized error to Express for the application's 500 handler; it does not expose the underlying exception. No last-used timestamp is updated. Redact credentials in application logging and error middleware too.

An empty whitelist denies every request. Exact IPv4, IPv6 and CIDR entries are supported using proxy-addr; invalid configuration throws a generic error when creating middleware. A disallowed source receives 403 { "error": "Forbidden" }. The middleware uses Express req.ip, which respects the application's trusted proxy configuration. It does not read forwarding headers independently.

Do not blindly use app.set('trust proxy', true). Configure only the actual trusted proxy addresses/hops for your topology, ensure proxies replace untrusted forwarding headers, and restrict direct access appropriately. Otherwise an attacker may influence the source IP used by the whitelist.

To customize responses, provide onUnauthorized(error, req, res) or onForbidden(error, req, res). The status is set to 401 or 403 before the callback; the callback must finish the response. Errors contain only fixed generic messages.

Explicit department context

import {
  extractDepartmentId,
  requireDepartmentId,
} from 'internal-dw-api-auth';

// Install after authentication on the existing internal router.
internalRouter.use((req, res, next) => {
  try {
    const departmentId = extractDepartmentId(req.headers, {
      validate: value => /^[A-Za-z0-9_-]+$/.test(value),
    });
    if (req.internalRequestContext) {
      req.internalRequestContext.departmentId = departmentId;
    }
    next();
  } catch {
    res.status(400).json({ error: 'Invalid department context' });
  }
});

// Copy these fields into your existing GraphQL server configuration:
const context = async ({ req }) => ({
  client: req.internalRequestContext?.client,
  departmentId: req.internalRequestContext?.departmentId,
});

// Inside a department-scoped resolver, pass the value explicitly to services:
const departmentId = requireDepartmentId(graphqlContext.departmentId);
await departmentService.list({ departmentId });

Header names are case-insensitive. String and string-array values are trimmed; identical repeated values are accepted, conflicting values are rejected. Missing values return undefined. Empty values, invalid types, commas (possibly combined headers), NUL and line breaks are rejected. Custom validators return a boolean; validator exceptions become a generic DepartmentContextError. requireDepartmentId rejects a missing/invalid value and returns the trimmed ID. Apply your custom validation during extraction. The library neither queries departments nor grants department access, and it needs no global context library. Authentication initializes fresh context, so attach department context after it.

Outbound headers

import { createInternalRequestHeaders } from 'internal-dw-api-auth';

const headers = createInternalRequestHeaders({
  apiKey: configuredApiKey,
  departmentId, // Explicit for each scoped request; omit for unscoped operations.
});
// Pass headers to your application's existing HTTP transport.

This returns Content-Type: application/json, Authorization: ApiKey ... and, when provided, X-Department-Id. Never log the returned headers. This first version provides the header helper only; callers own HTTP transport, timeouts, GraphQL response parsing and error handling. Avoid automatically retrying mutations.

Placeholder configuration, read by the application only:

INTERNAL_API_KEY=example_key_id.example_secret
INTERNAL_API_ALLOWED_NETWORKS=127.0.0.1,::1

Split the network configuration on commas and trim entries before passing it to the middleware. Decide explicitly whether missing configuration denies all access or prevents your application from starting.

Rotation and future authentication

Generate a new key, store its ID and hash, securely provision the raw key to the caller, then revoke the previous key after the rollout. Applications choose any overlap period and expiration policy. Multiple keys may map to one client.

AuthenticatedClient contains clientId, clientCode and authenticationType. The latter is 'api-key' | 'access-token' for future bearer-token compatibility; this version produces only 'api-key' and rejects bearer authentication.

Public API

Functions: generateApiKey, parseApiKey, hashApiKeySecret, verifyApiKeySecret, createApiKeyAuthenticationMiddleware, createIpWhitelistMiddleware, extractDepartmentId, requireDepartmentId, createInternalRequestHeaders.

Errors: ApiKeyFormatError, InternalAuthenticationError, DepartmentContextError, IpNotAllowedError.

Types: GeneratedApiKey, ApiKeyRecord, FindApiKey, AuthenticatedClient, InternalRequestContext, ApiKeyAuthenticationOptions, IpWhitelistOptions, DepartmentValidationOptions, InternalRequestHeadersOptions.

Only the package root is a supported entry point. Express request augmentation is included automatically when importing the package.

Development and publishing

npm ci
npm run build
npm run typecheck
npm run lint
npm pack --dry-run

Build output is CommonJS .js and .d.ts declarations without source maps. The publish allowlist includes dist, README.md and LICENSE; npm also includes package.json. Public access is configured. An authorized scope owner must publish the package using their own npm credentials. No automated tests are included.

MIT License. Copyright (c) 2026 Anja Tomovska.