Implementing Authorization
1. Creating Role-Based Middleware
Example: requireRole factory
export const requireRole = ( ... roles ) => ( req , res , next ) => {
if ( ! req.user) return next ( new HttpError ( 401 ));
if ( ! roles. includes (req.user.role)) return next ( new HttpError ( 403 ));
next ();
};
app. delete ( "/admin/users/:id" , requireAuth, requireRole ( "admin" ), removeUser);
2. Checking User Roles
Source Where stored
JWT claim req.user.role after verify
Session req.session.user.role
Database Re-fetch on each request (always-fresh, slower)
3. Implementing Permission Checks
Example: Permission helper
const PERMS = {
admin: [ "user:*" , "post:*" ],
editor: [ "post:read" , "post:write" ],
user: [ "post:read" ]
};
export function hasPermission ( role , perm ) {
return ( PERMS [role] || []). some ( p => p === perm || p. endsWith ( ":*" ) && perm. startsWith (p. slice ( 0 , - 1 )));
}
export const can = ( perm ) => ( req , res , next ) =>
hasPermission (req.user?.role, perm) ? next () : next ( new HttpError ( 403 ));
4. Using Access Control Lists
Example: ACL with accesscontrol
import AccessControl from "accesscontrol" ;
const ac = new AccessControl ();
ac. grant ( "user" ). readOwn ( "post" ). createOwn ( "post" );
ac. grant ( "admin" ). extend ( "user" ). readAny ( "post" ). deleteAny ( "post" );
const permission = ac. can (req.user.role). readAny ( "post" );
if ( ! permission.granted) throw new HttpError ( 403 );
5. Protecting Resource Access
Example: Owner-or-admin
app. delete ( "/posts/:id" , requireAuth, async ( req , res , next ) => {
const post = await db.posts. findById (req.params.id);
if ( ! post) return next ( new HttpError ( 404 ));
if (post.authorId !== req.user.sub && req.user.role !== "admin" ) {
return next ( new HttpError ( 403 ));
}
await db.posts. remove (req.params.id);
res. sendStatus ( 204 );
});
6. Implementing Attribute-Based Control (ABAC)
Example: Policy function
// policies/post.js
export const canEditPost = ( user , post ) =>
user.role === "admin" ||
(post.authorId === user.sub && ! post.locked && post.status !== "published" );
// route
if ( ! canEditPost (req.user, post)) throw new HttpError ( 403 );
Model Decision based on
RBAC Role only
ABAC User + resource + environment attributes
ReBAC Relationships (Zanzibar / OpenFGA / Cerbos)
7. Creating Authorization Middleware Chain
Example: Compose
app. patch ( "/orgs/:orgId/projects/:id" ,
requireAuth,
requireOrgMember (), // sets req.membership
requireRole ( "owner" , "editor" ),
loadProject (), // sets req.project
authorize (( req ) => canEditProject (req.user, req.project)),
updateProject
);
8. Handling Unauthorized Access
Status Meaning
401 Unauthorized Missing or invalid credentials — challenge with WWW-Authenticate
403 Forbidden Authenticated but not allowed — do not retry
Example: 401 vs 403
if ( ! req.user) {
res. set ( "WWW-Authenticate" , 'Bearer realm="api"' );
throw new HttpError ( 401 );
}
if ( ! hasPermission (req.user.role, "post:delete" )) throw new HttpError ( 403 );
9. Using Scopes for API Access
Example: OAuth scopes
export const requireScope = ( ... scopes ) => ( req , res , next ) => {
const token = req.user?.scope?. split ( " " ) || [];
const ok = scopes. every ( s => token. includes (s));
ok ? next () : next ( new HttpError ( 403 , "Insufficient scope" ));
};
app. get ( "/me/email" , requireAuth, requireScope ( "read:email" ), getEmail);
10. Implementing Resource-Level Permissions
Example: Per-row check
// Filter at query time — never trust client to filter
const posts = await prisma.post. findMany ({
where: {
OR: [
{ authorId: req.user.sub },
{ collaborators: { some: { userId: req.user.sub } } },
... (req.user.role === "admin" ? [{}] : [])
]
}
});
Warning: Always enforce row-level access in the database query, not in JS after fetching. Otherwise IDOR vulnerabilities arise.