auth-cloud-lib
v1.1.3
Published
Authentication and authorization middleware library for Express
Readme
auth-cloud-lib
Express authentication and authorization middleware library.
Exposes four main things:
| Export | Type | Description |
|---|---|---|
| AuthClient | class | Main entry point — instantiate once, use everywhere |
| authClient.checkUserAuthentication | method → middleware | Validates JWT from Authorization header |
| authClient.checkUserAuthorization | method → middleware | Enforces an authorization policy |
| createRule | namespace | Stateless rule factories |
| createPolicy | namespace | Stateless policy composers |
Installation
# The library itself
npm install auth-cloud-lib
# Peer dependency
npm install express
# Install your preferred Redis/Valkey client (pick one)
npm install ioredis
# or
npm install iovalkeySetup
Instantiate AuthClient once at app startup — before any route is registered — and reuse the instance everywhere.
import { AuthClient } from 'auth-cloud-lib'
import { Redis } from 'ioredis' // or: import { Redis } from 'iovalkey'
import fs from 'fs'
const authClient = new AuthClient({
// Your Redis or Valkey client instance
client: new Redis(process.env.REDIS_URL),
jwt: {
// RSA public key — pass the file content, not the path
publicKey: fs.readFileSync(process.env.RSA_PUBLIC_KEY_PATH, 'utf8'),
// Must match the `iss` claim used when the tokens were signed
serviceName: process.env.SERVICE_NAME,
},
// Required only when using authClient.createRule.systemPermission
systemEntityId: process.env.SYSTEM_ENTITY_ID,
// Optional: e.g. set user on Sentry when authenticated
onUserAuthenticated: (userId) => Sentry.setUser({ id: userId }),
})
export default authClientSentry / monitoring
The library does not depend on Sentry. To associate the authenticated user with Sentry (or any other tool), pass onUserAuthenticated in the config. It is called with the user ID whenever authentication succeeds (any token type).
import * as Sentry from '@sentry/node'
const authClient = new AuthClient({
...config,
onUserAuthenticated: (userId) => Sentry.setUser({ id: userId }),
})Sentry must be initialized in your app (e.g. Sentry.init({ ... })) before the first request; the library only invokes the callback.
checkUserAuthentication
Validates the JWT in the Authorization: Bearer <token> header.
On success populates req.user with { id, token, sessionId }.
On failure throws AuthenticationError (HTTP 401).
import { AuthTokenType } from 'auth-cloud-lib'
import authClient from './authClient.js'
// Protect a route with an access token
router.get('/me',
authClient.checkUserAuthentication(AuthTokenType.AccessToken),
(req, res) => res.json({ userId: req.user!.id }),
)
// Protect a token-refresh route
router.post('/refresh',
authClient.checkUserAuthentication(AuthTokenType.RefreshToken),
refreshHandler,
)
// Account recovery flow
router.post('/reset-password',
authClient.checkUserAuthentication(AuthTokenType.RecoveryToken),
resetPasswordHandler,
)
// Service-to-service exchange token
router.post('/exchange',
authClient.checkUserAuthentication(AuthTokenType.ExchangeToken),
exchangeHandler,
)Token types
| Constant | Value | When to use |
|---|---|---|
| AuthTokenType.AccessToken | ACCESS_TOKEN | Regular API requests |
| AuthTokenType.RefreshToken | REFRESH_TOKEN | Rotating expired access tokens |
| AuthTokenType.RecoveryToken | RECOVERY_TOKEN | Account recovery (one-time use) |
| AuthTokenType.ExchangeToken | EXCHANGE_TOKEN | Cross-service credential exchange |
checkUserAuthentication + checkUserAuthorization
Authorization always comes after authentication in the middleware chain.
import { createRule, AuthTokenType } from 'auth-cloud-lib'
import authClient from './authClient.js'
router.get('/:orgId/data',
authClient.checkUserAuthentication(AuthTokenType.AccessToken), // 1. who are you?
authClient.checkUserAuthorization(createRule.entityOwner('orgId')), // 2. are you allowed?
handler,
)createRule
Stateless factory functions — no need for the authClient instance.
createRule.entityOwner(attribute, location?, checkParents?)
Passes if the user owns the entity. Checks parent entities by default.
import { createRule, EntityIdLocation } from 'auth-cloud-lib'
createRule.entityOwner('orgId')
// → checks req.params.orgId, includes parents
createRule.entityOwner('orgId', EntityIdLocation.Query)
// → checks req.query.orgId
createRule.entityOwner('orgId', EntityIdLocation.Params, false)
// → checks req.params.orgId, no parent traversalcreateRule.entityMember(attribute, location?, checkParents?)
Passes if the user is a member of the entity.
createRule.entityMember('orgId')
// → checks req.params.orgId
createRule.entityMember('orgId', EntityIdLocation.Body)
// → checks req.body.orgId
createRule.entityMember('orgId', EntityIdLocation.Body, false)
// → no parent traversalcreateRule.entityPermission(permissions, attribute, location?, checkParents?)
Passes if the user has at least one of the required permissions on the entity.
createRule.entityPermission('org:manage', 'orgId')
// → user must have 'org:manage' on req.params.orgId
createRule.entityPermission(['org:read', 'org:write'], 'orgId')
// → user must have 'org:read' OR 'org:write'
createRule.entityPermission('data:write', 'orgId', EntityIdLocation.Body, false)
// → reads from req.body.orgId, no parent traversalauthClient.createRule.systemPermission(permissions)
Passes if the user has the required permission on the system entity (the global top-level entity).
Requires systemEntityId to be set in the config.
authClient.createRule.systemPermission('apps:create')
authClient.createRule.systemPermission(['apps:read', 'apps:write'])createPolicy
Composes multiple rules. Policies can be nested inside other policies.
createPolicy.or(rules) — default
Passes if any rule passes.
import { createRule, createPolicy } from 'auth-cloud-lib'
createPolicy.or([
createRule.entityOwner('orgId'),
createRule.entityMember('orgId'),
])createPolicy.and(rules)
Passes only if all rules pass.
createPolicy.and([
createRule.entityMember('orgId'),
createRule.entityPermission('data:write', 'orgId'),
])Nested policies
// Owner OR (member AND has write permission)
createPolicy.or([
createRule.entityOwner('orgId'),
createPolicy.and([
createRule.entityMember('orgId'),
createRule.entityPermission('data:write', 'orgId'),
]),
])Complete route example
import express from 'express'
import { createRule, createPolicy, AuthTokenType } from 'auth-cloud-lib'
import authClient from './authClient.js'
const router = express.Router()
// Anyone authenticated can list
router.get('/:orgId/items',
authClient.checkUserAuthentication(AuthTokenType.AccessToken),
authClient.checkUserAuthorization(createPolicy.or([
createRule.entityOwner('orgId'),
createRule.entityMember('orgId'),
])),
listItemsHandler,
)
// Only owners or users with write permission can create
router.post('/:orgId/items',
authClient.checkUserAuthentication(AuthTokenType.AccessToken),
authClient.checkUserAuthorization(createPolicy.or([
createRule.entityOwner('orgId'),
createRule.entityPermission('items:write', 'orgId'),
])),
createItemHandler,
)
// System-level admin only
router.delete('/admin/nuke',
authClient.checkUserAuthentication(AuthTokenType.AccessToken),
authClient.checkUserAuthorization(authClient.createRule.systemPermission('admin:all')),
nukeHandler,
)EntityIdLocation
Controls where the entity ID is read from.
| Constant | Value | Source |
|---|---|---|
| EntityIdLocation.Params | params | req.params (default) |
| EntityIdLocation.Query | query | req.query |
| EntityIdLocation.Body | body | req.body (supports dot-notation: 'org.id') |
| EntityIdLocation.Env | env | process.env (or envVars from config if provided) |
For EntityIdLocation.Env you do not need to pass anything: the library reads from process.env. Pass envVars in the config only if you want a different source (e.g. your validated env object from envalid).
TypeScript — req.user type
Importing anything from auth-cloud-lib automatically augments the Express Request type:
req.user // { id: string, token: string, sessionId: string } | undefined
req.serviceAccount // { id: string } | undefinedNo additional setup is needed.
Build
npm run build # compiles src/ → dist/
npm run dev # watch mode