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

@advcomm/uids-io-auth

v1.1.0

Published

Production-ready auth package with OAuth/OIDC, sessions, devices, and Express integration

Readme

@advcomm/uids-io-auth

Production-ready authentication for Node.js backends: OAuth 2.0/OIDC, sessions, refresh tokens, SDK-registered device tracking, and an optional Express adapter.

The package is not Express-only — services (AuthService, TokenService, etc.) are framework-agnostic. Use createAuthRouter when you want a ready-made HTTP surface on Express.

Installation

npm install @advcomm/uids-io-auth pg express

| Dependency | Role | |------------|------| | pg | Required — PostgreSQL access | | express | Optional peer — only needed for createAuthRouter / requireAuth |

Subpath export (if you split Express-only imports):

import { createAuthRouter } from '@advcomm/uids-io-auth/express';

Environment variables

Use these in your auth server (see examples/express-auth-server/.env.example):

| Variable | Required | Description | |----------|----------|-------------| | DATABASE_URL | Yes | PostgreSQL connection string | | ISSUER | Yes | Public auth issuer URL (e.g. https://auth.example.com) | | API_AUDIENCE | Yes | Resource server audience for access tokens | | CSRF_SECRET | Yes (prod) | Secret for signing CSRF/session cookies — must be set explicitly in production (no fallback) | | GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | If using Google | OAuth client credentials | | MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRET | If using Microsoft | Entra app credentials | | MICROSOFT_TENANT | No | Default common | | LOG_LEVEL | No | Pino level: debug, info, warn, error (default: debug in dev, info in production) |

Register OAuth clients for each portal with OAuthClientService.upsertPublicClient (see Portal OAuth clients). The example auth server uses a local seed helper, not a package export.

Database migrations

Migrations are not run automatically. Call explicitly on startup:

import { Pool } from 'pg';
import { runAuthMigrations } from '@advcomm/uids-io-auth';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
await runAuthMigrations(pool);

Auth server (auth.example.com)

import express from 'express';
import { Pool } from 'pg';
import {
  createAuthKit,
  createAuthRouter,
  OAuthClientService,
  runAuthMigrations,
} from '@advcomm/uids-io-auth';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
await runAuthMigrations(pool);

// Register one public OAuth client per portal (PKCE). Repeat per app.
const oauthClients = new OAuthClientService(pool);
await oauthClients.upsertPublicClient({
  id: 'merchant_portal_web',
  name: 'Merchant Portal Web',
  redirectUris: ['https://merchant.example.com/auth/callback'],
});

const authKit = await createAuthKit({
  issuer: process.env.ISSUER!,
  apiAudience: process.env.API_AUDIENCE!,
  pg: pool,
  cookie: {
    name: 'uids_auth_session',
    domain: '.example.com',
    secure: true,
    sameSite: 'lax',
  },
  csrf: { secret: process.env.CSRF_SECRET! },
  providers: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      callbackUrl: `${process.env.ISSUER}/oauth/google/callback`,
    },
    microsoft: {
      clientId: process.env.MICROSOFT_CLIENT_ID!,
      clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
      tenant: process.env.MICROSOFT_TENANT ?? 'common',
      callbackUrl: `${process.env.ISSUER}/oauth/microsoft/callback`,
    },
  },
  email: {
    sendMagicLink: async (email, url) => {
      // integrate with your email provider
    },
  },
});

const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use('/', createAuthRouter(authKit));
app.listen(3000);

createAuthRouter mounts:

  • OIDC/.well-known/openid-configuration, /.well-known/jwks.json, /login
  • OAuth/authorize, /token, provider callbacks, /logout
  • Email — magic link and password login routes
  • Sessions — session cookie introspection and revoke
  • Devices — register, list, revoke (CSRF-protected where required)
  • Middleware — CORS, CSRF on state-changing routes, Zod validation, centralized error handling

See examples/express-auth-server.

API docs (Bruno): Import OpenCollection bruno/uids-auth-api into your Bruno workspace alongside backend service collections — see bruno/README.md.

API server (api.example.com)

import express from 'express';
import { requireAuth } from '@advcomm/uids-io-auth';

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

app.use(requireAuth({
  issuer: process.env.ISSUER!,
  audience: process.env.API_AUDIENCE!,
  jwksUrl: `${process.env.ISSUER}/.well-known/jwks.json`,
}));

app.get('/me', (req, res) => {
  res.json({ auth: req.auth });
});

Configure CORS on the API to allow your portal origins. This package does not set API CORS headers.

See examples/express-api-server.

Errors and logging

The Express adapter uses two response shapes so OAuth clients and REST portals both get usable errors.

Validation errors (Zod, HTTP 422)

Routes validated with the built-in middleware return all field issues:

{
  "success": false,
  "message": "Validation failed",
  "error": {
    "code": "VALIDATION_ERROR",
    "details": [
      { "field": "client_id", "message": "Required" },
      { "field": "platform", "message": "Invalid enum value..." }
    ]
  }
}

Use ValidationError, isValidationError, and ValidationDetail from the package if you handle errors in custom middleware.

OAuth / auth errors (HTTP 4xx)

Business and OAuth-style failures use the familiar shape:

{
  "error": "invalid_request",
  "error_description": "Invalid refresh token"
}

Other exported errors: UnauthorizedError, ForbiddenError, ConflictError, RateLimitError, InvalidRequestError, and base AuthError.

Server errors (HTTP 500)

Unexpected errors return a generic body (no stack or internal details). Full error context is logged server-side only.

Structured logs (Pino)

The router logs via Pino to stdout:

| Situation | Level | What is logged | |-----------|-------|----------------| | Request validation failed | warn | scope, field names, issue count (not request body values) | | Expected auth errors | info | error code, status, method, path | | Unexpected errors | error | error name/message, method, path |

Set LOG_LEVEL=debug locally. In production, logs are JSON (no pretty-print).

Tracing: pass X-Request-Id from your gateway or API; it is included in log context when present.

Google Cloud OAuth setup

  1. Create an OAuth 2.0 Client ID (Web application) in Google Cloud Console.
  2. Authorized redirect URI: https://auth.example.com/oauth/google/callback
  3. Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in your auth server environment.

Microsoft Entra setup

  1. Register an application in Microsoft Entra ID.
  2. Add redirect URI: https://auth.example.com/oauth/microsoft/callback
  3. Create a client secret.
  4. Set MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET, and configure tenant (common, organizations, consumers, or a tenant ID).

Portal OAuth clients

Each portal is a public OAuth client (PKCE, no client secret in the browser):

import { OAuthClientService } from '@advcomm/uids-io-auth';

const oauthClients = new OAuthClientService(pool);
await oauthClients.upsertPublicClient({
  id: 'your_portal_web',
  name: 'Your Portal Web',
  redirectUris: ['https://your-portal.example.com/auth/callback'],
  // origins optional — derived from redirect URIs when omitted
});

For local dev with multiple UIDs portals, see examples/express-auth-server/seedPortalClients.ts (merchant_portal_web, agency_portal_web, etc.). That helper is not exported from the package.

Login flow (PKCE)

  1. Portal generates PKCE verifier/challenge and optional SDK device_id.
  2. Portal redirects user to GET /authorize?response_type=code&client_id=...&redirect_uri=...&scope=openid profile email&state=...&code_challenge=...&code_challenge_method=S256
  3. User authenticates on auth domain (Google, Microsoft, or email).
  4. Auth domain redirects to portal redirect_uri?code=...&state=...
  5. Portal calls POST /token with grant_type=authorization_code, code, code_verifier, client_id, redirect_uri.
  6. Portal receives access_token, refresh_token, and optional id_token.
  7. Portal calls API with Authorization: Bearer {access_token}.

Device identity

Companion client SDKs (React, Flutter, native) generate a stable UUID device_id, register it via POST /devices/register, and send X-Uids-Device-Id on auth flows. The auth server binds devices to users and includes device_id in access token claims.

Supported platforms: web, ios, android, desktop, unknown (validated on register).

See docs/sdk-contract.md for the full client/server contract.

Recommended companion SDKs (future packages)

| Platform | Package | Storage | |----------|---------|---------| | React / Next.js | @uids-io/auth-react | localStorage / IndexedDB | | Flutter web + mobile | @uids-io/auth-flutter | shared_preferences / Keychain | | iOS / Android native | @uids-io/auth-native | Keychain / EncryptedSharedPreferences | | Desktop | @uids-io/auth-react or native wrapper | OS keychain |

Exports

Kit & HTTP

  • createAuthKit, createAuthRouter, requireAuth
  • runAuthMigrations
  • verifyAccessToken, generatePkcePair, verifyCodeChallenge

Services (use directly without Express)

  • AuthService, UserService, TokenService, SessionService, DeviceService, OAuthClientService

Errors

  • AuthError, InvalidRequestError, ValidationError, UnauthorizedError, ForbiddenError, ConflictError, RateLimitError
  • isAuthError, isValidationError, ValidationDetail

Types & helpers

  • AuthUser, AuthContext, Device, DevicePlatform, TokenResponse, provider mappers, etc.

Testing

npm test              # all tests
npm run test:unit     # crypto, redirect_uri, provider mapping
npm run test:integration  # DB + Express flows (uses pg-mem by default)
npm run typecheck
npm run build

Integration tests use pg-mem by default (no Docker required). Optional backends:

  • TEST_DATABASE_URL=postgres://... — run against an existing PostgreSQL instance
  • USE_TESTCONTAINERS=1 — use Docker testcontainers when available

Releases

Releases on main use semantic-release — see RELEASING.md. Use Conventional Commits (feat:, fix:, etc.) so version bumps and npm publish happen automatically.

License

MIT