dms-auth-check-npm
v1.0.0
Published
Shared Express middleware for verifying DMS Identity Service JWTs and enforcing permission-based authorization (requirePermission, requireAnyPermission, requireAllPermissions) across microservices.
Maintainers
Readme
dms-auth-check-npm
Shared Express middleware for verifying JWTs issued by a DMS-style
Identity Service and enforcing permission-based authorization
(dealer.profile.view, order.po.approve, etc.) — so every downstream
microservice (Dealer, Product, Order, Credit, Warehouse, ...) doesn't
need to reimplement authenticate + requirePermission from scratch.
Framework: Express. Module format: ESM. Zero database dependency — it
only verifies signatures and checks the permissions array already
embedded in the token.
Install
npm install dms-auth-check-npmExpected token shape
This package verifies tokens shaped like the ones issued by the
Identity Service's signAccessToken:
{
"userId": "...",
"companyId": "...",
"dealerId": null,
"employeeId": null,
"roleId": "...",
"userType": "MANUFACTURER_USER",
"isSuperAdmin": false,
"permissions": ["dealer.profile.view", "order.po.view"],
"iat": 1234567890,
"exp": 1234596690,
"jti": "..."
}permissions is null for super admins, meaning "all permissions
allowed" — every require* check short-circuits to true for them.
Usage
import express from "express";
import { createDmsAuth, dmsAuthErrorHandler } from "dms-auth-check-npm";
const { authenticate, requirePermission, requireAnyPermission, requireAllPermissions } =
createDmsAuth({
jwtSecret: process.env.JWT_SECRET, // must match the Identity Service's JWT_SECRET
});
const router = express.Router();
router.use(authenticate);
router.post(
"/dealers",
requirePermission("dealer.profile.create"),
createDealerHandler
);
router.get(
"/finance-summary",
requireAnyPermission(["credit.account.view", "payment.transaction.view"]),
financeSummaryHandler
);
router.post(
"/orders/:id/approve",
requireAllPermissions(["order.po.approve", "approval.request.approve"]),
approveOrderHandler
);
// Turns thrown AuthError instances into { success:false, message } JSON.
// Mount after your routes; skip this if your own error handler already
// reads err.statusCode / err.message.
app.use(dmsAuthErrorHandler);After authenticate runs, req.auth is populated:
req.auth = {
userId, companyId, dealerId, employeeId,
roleId, userType, isSuperAdmin,
permissions, // string[] | null
jti, exp,
};Checking token revocation
This package has no database dependency, so token revocation (logout)
is opt-in via a hook you supply — wire it to whatever store your
service already uses (Mongo revoked_tokens collection, Redis, a
shared cache, etc.):
import RevokedToken from "./models/revoked-token.model.js";
const { authenticate } = createDmsAuth({
jwtSecret: process.env.JWT_SECRET,
isTokenRevoked: async (payload) => {
if (!payload.jti) return false;
return Boolean(await RevokedToken.exists({ jti: payload.jti }));
},
});If you don't pass isTokenRevoked, revocation is skipped entirely —
tokens are valid until they expire (exp), which is the simplest and
most common setup for short-lived tokens.
API
createDmsAuth({ jwtSecret, isTokenRevoked? })→{ authenticate, requirePermission, requireAnyPermission, requireAllPermissions, hasPermission }authenticate(req, res, next)— verifies theAuthorization: Bearer <token>header, populatesreq.auth, callsnext(AuthError)on failure.requirePermission(code)— returns middleware; 403s ifreq.auth.permissionsdoesn't includecode(and caller isn't a super admin).requireAnyPermission(codes[])— passes if at least one code matches.requireAllPermissions(codes[])— passes only if every code matches.hasPermission(auth, code)— the underlying boolean check, exported in case you need it outside a middleware (e.g. to filter a list of UI menu items).verifyDmsToken(token, secret)— lower-level: verify + decode without touchingreq.AuthError— thrown/passed-to-next()on auth failures; has.statusCode(401 or 403) and.message.dmsAuthErrorHandler— optional Express error middleware that serializesAuthErrorto JSON.
Why permissions live in the token (and the trade-off)
The Identity Service embeds the caller's resolved permission codes
directly in the JWT at login time, so downstream services never need to
call back into the Identity Service or share a permissions cache just
to authorize a request — requirePermission is a pure in-memory check
against the already-verified token.
The trade-off: if an admin changes a role's permissions, that change
does not apply to a user's already-issued token until it expires or
they log in again. Keep your Identity Service's JWT_EXPIRES_IN short
enough that this staleness window is acceptable for your use case.
License
MIT
