const rolePermissions = {
guest: ['article:read'],
author: ['article:read', 'article:create', 'article:update:own'],
editor: ['article:publish', 'article:update:any'],
admin: ['user:manage', 'role:assign']
};
const roleHierarchy = {
admin: ['editor'],
editor: ['author'],
author: ['guest']
};
function resolvePermissions(role) {
const seen = new Set();
const perms = new Set();
const stack = [role];
while (stack.length) {
const current = stack.pop();
if (!current || seen.has(current)) continue;
seen.add(current);
for (const p of rolePermissions[current] || []) perms.add(p);
for (const parent of roleHierarchy[current] || []) stack.push(parent);
}
return perms;
}
function can(role, permission) {
return resolvePermissions(role).has(permission);
}
module.exports = { resolvePermissions, can, rolePermissions };
const jwt = require('jsonwebtoken');
const { can } = require('./permissions');
function requireAuth(req, res, next) {
const header = req.headers.authorization || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
if (!token) return res.status(401).json({ error: 'authentication_required' });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = { id: payload.sub, role: payload.role };
return next();
} catch (err) {
return res.status(401).json({ error: 'invalid_token' });
}
}
function requirePermission(permission) {
return function (req, res, next) {
if (!req.user) return res.status(401).json({ error: 'authentication_required' });
if (!can(req.user.role, permission)) {
return res.status(403).json({ error: 'forbidden', required: permission });
}
return next();
};
}
function requireAny(...permissions) {
return function (req, res, next) {
if (!req.user) return res.status(401).json({ error: 'authentication_required' });
const ok = permissions.some((p) => can(req.user.role, p));
if (!ok) return res.status(403).json({ error: 'forbidden', requiredAny: permissions });
return next();
};
}
module.exports = { requireAuth, requirePermission, requireAny };
const express = require('express');
const { requireAuth, requirePermission, requireAny } = require('./authorize');
const controller = require('./articles.controller');
const router = express.Router();
router.get('/', requireAuth, requirePermission('article:read'), controller.list);
router.post('/', requireAuth, requirePermission('article:create'), controller.create);
router.patch(
'/:id',
requireAuth,
requireAny('article:update:any', 'article:update:own'),
controller.update
);
router.post(
'/:id/publish',
requireAuth,
requirePermission('article:publish'),
controller.publish
);
module.exports = router;
This snippet implements a small but realistic role-based access control (RBAC) layer for an Express API, split across the permission model, the guard middleware, and the router that consumes it. The core idea is to keep authorization declarative: routes state which permission they require, and the machinery of resolving roles into concrete permissions lives in one place. This avoids scattering if (user.role === 'admin') checks across handlers, which is the usual source of privilege bugs.
In permissions.js, roles are mapped to sets of fine-grained permission strings like article:publish. Modeling capabilities as permissions rather than checking roles directly is what makes RBAC scale — a new role is just a new entry, and a handler never needs to know which roles happen to include a capability. roleHierarchy lets higher roles inherit everything a lower role can do, and resolvePermissions flattens that hierarchy into a Set for O(1) lookups. can is the single predicate every check funnels through.
In authorize.js, requireAuth verifies the JWT and attaches a normalized req.user, deliberately separating authentication (who you are) from authorization (what you may do). requirePermission is a middleware factory: it returns a closure bound to a specific permission so routes read like requirePermission('article:publish'). It responds 401 when there is no authenticated user and 403 when the user is known but lacks the capability — a distinction clients rely on. requireAny supports OR-style checks where any one of several permissions suffices.
In articles.routes.js, the middleware is composed per route, running requireAuth before each permission guard so req.user is always populated. Because guards are ordinary middleware, they short-circuit the chain before the handler runs, keeping controllers free of authorization logic.
A key trade-off is that this model is permission-centric, not resource-centric: it answers "can this role publish articles" but not "does this user own this particular article." Ownership checks still belong in the handler. The pitfall to avoid is trusting role claims embedded in a token without re-resolving permissions server-side, which is why resolution happens from the trusted permissions.js map on every request.
Related snips
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: String!
GraphQL API with Spring Boot
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
timeout: 15000,
Axios API client with interceptors
Share this code
Here's the card — post it anywhere.