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

@uoj-lk/auth-node

v1.2.0

Published

Lightweight Node.js authentication middleware for MyUOJ Auth Service with JWT validation, RBAC, and Redis caching for microservices

Readme

@uoj-lk/auth-node

Production-ready authentication middleware for Node.js microservices with JWT validation, RBAC, Redis caching, and enterprise-grade security.

npm version License: MIT Security: Audited Node: 18+


Why @uoj-lk/auth-node?

Built for the University of Jaffna's authentication infrastructure, this middleware provides enterprise-grade security with minimal overhead. Perfect for microservices that need centralized authentication without reinventing the wheel.

Performance: 95% faster auth with Redis caching (5-10ms vs 100-200ms)
Security: SSRF protection, cache integrity validation, token blacklisting
Observability: Built-in metrics, structured logging, health checks
DX: Drop-in Express middleware with TypeScript support


Quick Start

Installation

npm install @uoj-lk/auth-node express cookie-parser
# Optional: For caching
npm install redis

Basic Usage

const express = require("express");
const cookieParser = require("cookie-parser");
const authNode = require("@uoj-lk/auth-node");

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

// Initialize with secure defaults
const auth = authNode.init({
  authServiceUrl: process.env.AUTH_SERVICE_URL,
  jwtSecret: process.env.JWT_SECRET, // Min 32 chars
  jwtIssuer: "myuoj-auth",
  jwtAudience: "myuoj-services",
});

// Protected route
app.get("/api/protected", auth.authenticateJWT, (req, res) => {
  res.json({
    user: req.user,
    roles: req.roles,
    permissions: req.permissions,
  });
});

app.listen(3000);

Features

🔐 JWT Authentication

Validates JWT tokens from cookies or Authorization headers with automatic caching.

// Require authentication
app.get("/api/users", auth.authenticateJWT, (req, res) => {
  // req.user, req.roles, req.permissions available
  res.json({ users: [] });
});

// Optional authentication
app.get("/api/public", auth.optionalAuth, (req, res) => {
  const message = req.user ? `Welcome ${req.user.username}` : "Welcome guest";
  res.json({ message });
});

🛡️ Role-Based Access Control (RBAC)

// Require specific role
app.delete(
  "/api/admin/users/:id",
  auth.authenticateJWT,
  auth.requireRole("admin"),
  (req, res) => {
    // Only admins can access
  }
);

// Require specific permission
app.post(
  "/api/users",
  auth.authenticateJWT,
  auth.requirePermission("users:write"),
  (req, res) => {
    // Only users with 'users:write' permission
  }
);

// Require multiple permissions (ALL)
app.put(
  "/api/users/:id",
  auth.authenticateJWT,
  auth.requirePermission("users:read", "users:write"),
  (req, res) => {
    // User must have BOTH permissions
  }
);

// Require any permission (OR)
app.get(
  "/api/dashboard",
  auth.authenticateJWT,
  auth.requireAnyPermission("users:read", "posts:read", "dashboard:view"),
  (req, res) => {
    // User needs at least ONE permission
  }
);

⚡ Redis Caching

Reduce auth service load by 90% with intelligent caching:

const { createClient } = require("redis");

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

const auth = authNode.init({
  authServiceUrl: process.env.AUTH_SERVICE_URL,
  jwtSecret: process.env.JWT_SECRET,
  redis,
  useCache: true,
  cacheTTL: 300, // 5 minutes
});

// First request: ~100-200ms (validates with auth service)
// Cached requests: ~5-10ms (reads from Redis)
// Automatic cache invalidation on user/role changes

Cache Features:

  • SHA256 integrity validation prevents cache poisoning
  • Automatic fallback if Redis unavailable
  • TTL-based expiration (default 5 minutes)
  • Manual invalidation support

🔒 Token Blacklisting

Revoke tokens immediately (logout, password reset, compromise):

app.post("/api/logout", auth.authenticateJWT, async (req, res) => {
  const jwt = require("jsonwebtoken");
  const payload = jwt.decode(req.token);

  if (payload?.jti) {
    // Blacklist until natural expiration
    const ttl = payload.exp - Math.floor(Date.now() / 1000);
    await auth.blacklistToken(payload.jti, ttl);
  }

  res.json({ success: true });
});

📊 Observability

Metrics

// JSON metrics
app.get("/api/metrics", (req, res) => {
  res.json(auth.getMetrics());
});

// Prometheus format
app.get("/metrics", (req, res) => {
  res.set("Content-Type", "text/plain");
  res.send(auth.getPrometheusMetrics());
});

Available Metrics:

  • Auth attempts, successes, failures
  • Cache hit/miss rates
  • RBAC checks and denials
  • Average response times
  • Error counts by type

Health Checks

app.get("/health", async (req, res) => {
  const health = await auth.getHealthStatus();
  res.status(health.healthy ? 200 : 503).json(health);
});

Response:

{
  "healthy": true,
  "timestamp": "2025-12-14T10:30:00Z",
  "components": {
    "authService": { "status": "healthy" },
    "cache": { "status": "healthy", "available": true }
  },
  "metrics": {
    "authAttempts": 1250,
    "cacheHitRate": 0.884
  }
}

Structured Logging

const { logger } = require("@uoj-lk/auth-node");

logger.info("User authenticated", { userId: req.user.id });
logger.warn("High failure rate detected", { failures: 50 });
logger.error("Auth service unreachable", { error: err.message });

Security

✅ Enterprise-Grade Protection

| Feature | Implementation | | ----------------------- | ------------------------------------------------------- | | JWT Secret Strength | Enforces 32+ characters with entropy validation | | SSRF Protection | Blocks private IPs, localhost, cloud metadata endpoints | | Cache Integrity | SHA256 hashes prevent poisoning attacks | | Token Revocation | Redis-backed blacklist for immediate invalidation | | Input Sanitization | Validates roles/permissions against injection | | Error Handling | Generic messages in production, detailed server logs |

🔒 Production Checklist

# Generate strong secret (32+ characters)
openssl rand -base64 32

# Environment variables
AUTH_SERVICE_URL=https://auth.myuoj.ac.lk  # HTTPS required
JWT_SECRET=your-cryptographically-random-secret
REDIS_URL=redis://redis-server:6379
NODE_ENV=production

Required:

  • [ ] JWT secret ≥32 characters
  • [ ] Auth service URL uses HTTPS
  • [ ] Redis configured for caching
  • [ ] NODE_ENV=production set
  • [ ] CORS configured for trusted origins
  • [ ] Rate limiting enabled
  • [ ] Health checks monitored

📖 Full Security Documentation | Security Audit Report


API Reference

Configuration

const auth = authNode.init({
  // Required
  authServiceUrl: string, // Auth service URL (HTTPS in production)
  jwtSecret: string, // JWT secret (min 32 characters)

  // Optional JWT validation
  jwtIssuer: string, // Expected JWT issuer
  jwtAudience: string, // Expected JWT audience

  // Optional caching
  redis: RedisClient, // Redis client instance
  useCache: boolean, // Enable caching (default: true)
  cacheTTL: number, // Cache TTL in seconds (default: 300)

  // Optional configuration
  cookieName: string, // Cookie name (default: 'accessToken')
  logLevel: string, // error|warn|info|debug (default: 'info')
  debug: boolean, // Detailed errors (default: false)
  allowInternalUrls: boolean, // Dev-only: allow localhost (default: false)
});

Middleware

// Authentication
auth.authenticateJWT; // Require JWT authentication
auth.optionalAuth; // Optional authentication

// Authorization (RBAC)
auth.requireRole(...roles); // Require specific role(s)
auth.requirePermission(...permissions); // Require all permissions
auth.requireAnyPermission(...permissions); // Require any permission

// Error handling
auth.errorHandler; // Error middleware (use last)
auth.notFoundHandler(); // 404 handler

Services

// Cache management
auth.initCache(redisClient);
auth.getCachedUser(userId);
auth.cacheUser(userId, userData, ttl);
auth.invalidateUserCache(userId);
auth.clearAllCache();
auth.getCacheStats();

// Token blacklisting
auth.blacklistToken(jti, ttl);
auth.isTokenBlacklisted(jti);

// Health & metrics
auth.getHealthStatus();
auth.checkAuthServiceHealth();
auth.getMetrics();
auth.getPrometheusMetrics();
auth.resetMetrics();

// Utilities
auth.hasPermission(req, permission);
auth.hasRole(req, role);
auth.isAuthenticated(req);
auth.getUserId(req);
auth.getUsername(req);
auth.extractToken(req, cookieName);

Request Object Enhancement

After authenticateJWT, the request object is enriched:

req.user; // { id, username, email, status, ... }
req.roles; // ['admin', 'editor']
req.permissions; // ['users:read', 'users:write']
req.roleDetails; // [{ name: 'admin', permissions: [...] }]
req.organizations; // [{ id, name, ... }]
req.token; // Original JWT string

Advanced Examples

Complete Express Server

const express = require("express");
const cookieParser = require("cookie-parser");
const cors = require("cors");
const { createClient } = require("redis");
const authNode = require("@uoj-lk/auth-node");

const app = express();

// Middleware
app.use(express.json());
app.use(cookieParser());
app.use(cors({ origin: process.env.CORS_ORIGIN, credentials: true }));

// Initialize Redis
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

// Initialize auth
const auth = authNode.init({
  authServiceUrl: process.env.AUTH_SERVICE_URL,
  jwtSecret: process.env.JWT_SECRET,
  jwtIssuer: "myuoj-auth",
  jwtAudience: "myuoj-services",
  redis,
  cacheTTL: 300,
  logLevel: "info",
});

// Public routes
app.get("/api/public/status", (req, res) => {
  res.json({ status: "ok" });
});

// Protected routes
app.get(
  "/api/users",
  auth.authenticateJWT,
  auth.requirePermission("users:read"),
  async (req, res) => {
    const users = await getUsers();
    res.json({ users });
  }
);

app.post(
  "/api/users",
  auth.authenticateJWT,
  auth.requirePermission("users:write"),
  async (req, res) => {
    const user = await createUser(req.body);
    res.json({ user });
  }
);

app.delete(
  "/api/users/:id",
  auth.authenticateJWT,
  auth.requirePermission("users:delete"),
  async (req, res) => {
    await deleteUser(req.params.id);
    res.json({ success: true });
  }
);

// Admin-only routes
app.get(
  "/api/admin/stats",
  auth.authenticateJWT,
  auth.requireRole("admin"),
  async (req, res) => {
    const stats = await getSystemStats();
    res.json({ stats });
  }
);

// Logout endpoint
app.post("/api/logout", auth.authenticateJWT, async (req, res) => {
  const jwt = require("jsonwebtoken");
  const payload = jwt.decode(req.token);

  if (payload?.jti) {
    const ttl = payload.exp - Math.floor(Date.now() / 1000);
    await auth.blacklistToken(payload.jti, ttl);
  }

  res.json({ success: true });
});

// Health & metrics
app.get("/health", async (req, res) => {
  const health = await auth.getHealthStatus();
  res.status(health.healthy ? 200 : 503).json(health);
});

app.get("/metrics", (req, res) => {
  res.set("Content-Type", "text/plain");
  res.send(auth.getPrometheusMetrics());
});

// Error handling
app.use(auth.errorHandler);
app.use(auth.notFoundHandler());

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Programmatic Permission Checks

app.get("/api/users/:id", auth.authenticateJWT, async (req, res) => {
  const userId = req.params.id;
  const user = await getUser(userId);

  // Only show sensitive data if user has permission
  if (auth.hasPermission(req, "users:read:sensitive")) {
    user.email = await getEmail(userId);
    user.phone = await getPhone(userId);
  }

  // Only admins can see deleted users
  if (user.status === "deleted" && !auth.hasRole(req, "admin")) {
    return res.status(404).json({ error: "User not found" });
  }

  res.json({ user });
});

Cache Invalidation Strategy

// Invalidate cache when user data changes
app.put(
  "/api/users/:id",
  auth.authenticateJWT,
  auth.requirePermission("users:write"),
  async (req, res) => {
    const userId = req.params.id;
    await updateUser(userId, req.body);

    // Clear cache for updated user
    await auth.invalidateUserCache(userId);

    res.json({ success: true });
  }
);

// Invalidate cache when roles change
app.post(
  "/api/users/:id/roles",
  auth.authenticateJWT,
  auth.requirePermission("users:roles:write"),
  async (req, res) => {
    const userId = req.params.id;
    await updateUserRoles(userId, req.body.roles);

    // Clear cache since permissions changed
    await auth.invalidateUserCache(userId);

    res.json({ success: true });
  }
);

Multiple Permission Strategies

// Flexible permission requirements
const requireUserAccess = (req, res, next) => {
  const targetUserId = req.params.id;
  const currentUserId = auth.getUserId(req);

  // Allow if user is accessing their own data OR has admin role
  if (targetUserId === currentUserId || auth.hasRole(req, "admin")) {
    return next();
  }

  // Or if user has the specific permission
  if (auth.hasPermission(req, "users:read:all")) {
    return next();
  }

  res.status(403).json({ error: "Insufficient permissions" });
};

app.get(
  "/api/users/:id",
  auth.authenticateJWT,
  requireUserAccess,
  async (req, res) => {
    const user = await getUser(req.params.id);
    res.json({ user });
  }
);

TypeScript Support

Full type definitions included:

import {
  init,
  AuthNodeConfig,
  AuthRequest,
  AuthMiddleware,
  UserData,
  HealthStatus,
  Metrics,
} from "@uoj-lk/auth-node";
import { Request, Response, NextFunction } from "express";

const config: AuthNodeConfig = {
  authServiceUrl: process.env.AUTH_SERVICE_URL!,
  jwtSecret: process.env.JWT_SECRET!,
  redis: redisClient,
  cacheTTL: 300,
};

const auth = init(config);

// Typed route handler
app.get(
  "/api/users",
  auth.authenticateJWT,
  (req: AuthRequest, res: Response) => {
    const userId: number = req.user!.id;
    const username: string = req.user!.username;
    const permissions: string[] = req.permissions;

    res.json({ userId, username, permissions });
  }
);

// Typed health check
app.get("/health", async (req: Request, res: Response) => {
  const health: HealthStatus = await auth.getHealthStatus();
  res.status(health.healthy ? 200 : 503).json(health);
});

// Typed metrics
app.get("/metrics", (req: Request, res: Response) => {
  const metrics: Metrics = auth.getMetrics();
  res.json(metrics);
});

Environment Variables

# Required
AUTH_SERVICE_URL=https://auth.myuoj.ac.lk
JWT_SECRET=your-cryptographically-random-32plus-character-secret

# Optional - Redis
REDIS_URL=redis://localhost:6379

# Optional - Configuration
NODE_ENV=production
LOG_LEVEL=info
CACHE_TTL=300

# Optional - JWT Validation
JWT_ISSUER=myuoj-auth
JWT_AUDIENCE=myuoj-services

# Optional - Development
AUTH_NODE_ALLOW_INTERNAL_URLS=false

Performance

Benchmarks

| Metric | Without Cache | With Cache | Improvement | | --------------------- | ------------- | --------------- | ----------------- | | Latency | 100-200ms | 5-10ms | 95% faster | | Throughput | 100-200 req/s | 1000-2000 req/s | 10x increase | | Auth Service Load | 100% | 10% | 90% reduction | | Cache Hit Rate | N/A | 80-95% | After warm-up |

Resource Usage

  • Memory: 50-100 MB baseline + cache overhead
  • CPU: <5% on modern systems
  • Network: 60-70% reduction with keep-alive connections

Troubleshooting

Common Issues

"Authentication required" (401)

// Check token is sent correctly
// Cookie: access_token=<jwt>
// Header: Authorization: Bearer <jwt>

// Verify cookie name matches config
const auth = authNode.init({
  cookieName: "accessToken", // Default
  // ...
});

"Invalid or expired token" (401)

// Check JWT configuration matches auth service
const auth = authNode.init({
  jwtSecret: process.env.JWT_SECRET, // Must match auth service
  jwtIssuer: "myuoj-auth", // Must match token
  jwtAudience: "myuoj-services", // Must match token
});

High cache miss rate

// Increase TTL if users have stable permissions
const auth = authNode.init({
  cacheTTL: 600, // 10 minutes instead of 5
  // ...
});

// Check Redis connection
redis.on("error", (err) => {
  console.error("Redis error:", err);
});

Auth service unreachable

// Verify URL and network
await auth.checkAuthServiceHealth();

// Check health status
const health = await auth.getHealthStatus();
console.log(health);

Debug Mode

// Enable detailed logging (development only)
const auth = authNode.init({
  debug: true,
  logLevel: "debug",
  // ...
});

// Access logger
const { logger } = require("@uoj-lk/auth-node");
logger.setLogLevel("debug");

Documentation


Related Packages


Support

  • Documentation: https://github.com/UoJ-LK/myuoj-auth/tree/main/auth-node
  • Issues: https://github.com/UoJ-LK/myuoj-auth/issues
  • NPM: https://www.npmjs.com/package/@uoj-lk/auth-node

License

MIT © University of Jaffna


Contributing

Contributions are welcome! Please read our Contributing Guide for details.


Built with ❤️ at the University of Jaffna