| name | authorization-patterns |
| description | Authorization patterns including RBAC and ABAC. Use when implementing access control. |
Authorization Patterns Skill
This skill covers authorization patterns for controlling access to resources.
When to Use
Use this skill when:
- Implementing role-based access control (RBAC)
- Setting up attribute-based access control (ABAC)
- Creating permission guards
- Controlling resource access
Core Principle
LEAST PRIVILEGE - Grant minimum permissions needed. Default to deny. Validate on every request.
Role-Based Access Control (RBAC)
Role Definition
export const Role = {
USER: 'USER',
MODERATOR: 'MODERATOR',
ADMIN: 'ADMIN',
} as const;
export type Role = (typeof Role)[keyof typeof Role];
export const Permission = {
USER_READ: 'user:read',
USER_CREATE: 'user:create',
USER_UPDATE: 'user:update',
USER_DELETE: 'user:delete',
POST_READ: 'post:read',
POST_CREATE: 'post:create',
POST_UPDATE: 'post:update',
POST_DELETE: 'post:delete',
ADMIN_ACCESS: 'admin:access',
} as const;
export type Permission = (typeof Permission)[keyof typeof Permission];
export const rolePermissions: Record<Role, Permission[]> = {
USER: [
Permission.USER_READ,
Permission.POST_READ,
Permission.POST_CREATE,
],
MODERATOR: [
Permission.USER_READ,
Permission.POST_READ,
Permission.POST_CREATE,
Permission.POST_UPDATE,
Permission.POST_DELETE,
],
ADMIN: Object.values(Permission),
};
Permission Checker
import { Role, Permission, rolePermissions } from './roles';
export function hasPermission(role: Role, permission: Permission): boolean {
return rolePermissions[role].includes(permission);
}
export function hasAnyPermission(role: Role, permissions: Permission[]): boolean {
return permissions.some((p) => hasPermission(role, p));
}
export function hasAllPermissions(role: Role, permissions: Permission[]): boolean {
return permissions.every((p) => hasPermission(role, p));
}
Authorization Middleware
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
import fp from 'fastify-plugin';
import { Permission, hasPermission, hasAnyPermission } from '../lib/permissions';
declare module 'fastify' {
interface FastifyInstance {
authorize: (...permissions: Permission[]) => (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;
authorizeAny: (...permissions: Permission[]) => (
request: FastifyRequest,
reply: FastifyReply
) => Promise<void>;
}
}
const authorizePlugin: FastifyPluginAsync = async (fastify) => {
fastify.decorate('authorize', {
(: , : ) => {
(!request.) {
reply.().({ : });
}
authorized = permissions.(
(request.., p)
);
(!authorized) {
reply.().({ : });
}
};
});
fastify.(, {
(: , : ) => {
(!request.) {
reply.().({ : });
}
authorized = (request.., permissions);
(!authorized) {
reply.().({ : });
}
};
});
};
(authorizePlugin);
Using Authorization
import { FastifyPluginAsync } from 'fastify';
import { Permission } from '../lib/roles';
const adminRoutes: FastifyPluginAsync = async (fastify) => {
fastify.addHook('preHandler', fastify.authenticate);
fastify.get('/dashboard', {
preHandler: [fastify.authorize(Permission.ADMIN_ACCESS)],
}, async () => {
return { stats: await getAdminStats(fastify) };
});
fastify.get('/users', {
preHandler: [fastify.authorize(Permission.USER_READ, Permission.ADMIN_ACCESS)],
}, async () => {
return fastify.db.user.findMany();
});
fastify.delete('/users/:id', {
preHandler: [fastify.authorize(.)],
}, (request) => {
{ id } = request. { : };
fastify...({ : { id } });
{ : };
});
};
adminRoutes;
Attribute-Based Access Control (ABAC)
Policy Definition
import { User, Post } from '@prisma/client';
interface PolicyContext {
user: User;
resource: unknown;
action: string;
environment?: {
time?: Date;
ip?: string;
};
}
type PolicyFunction = (context: PolicyContext) => boolean;
const policies: Record<string, PolicyFunction> = {
'post:read': ({ resource }) => {
const post = resource as Post;
return post.published;
},
'post:update': ({ user, resource }) => {
const post = resource as Post;
return post.authorId === user.id || user.role === 'ADMIN';
},
'post:delete': () => {
post = resource ;
post. === user. || user. === ;
},
: {
targetUser = resource ;
user. === targetUser. || user. === ;
},
: {
(user. !== ) ;
hour = environment?.?.() ?? ().();
hour >= && hour <= ;
},
};
(): {
policy = policies[action];
(!policy) {
;
}
({ ...context, action });
}
ABAC Middleware
import { FastifyPluginAsync, FastifyRequest, FastifyReply } from 'fastify';
import fp from 'fastify-plugin';
import { checkPolicy } from '../lib/abac/policies';
type ResourceLoader<T> = (request: FastifyRequest) => Promise<T>;
declare module 'fastify' {
interface FastifyInstance {
checkAccess: <T>(
action: string,
loadResource: ResourceLoader<T>
) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
}
}
const abacPlugin: FastifyPluginAsync = async (fastify) => {
fastify.decorate(
'checkAccess',
<T>(action: string, loadResource: ResourceLoader<T>) => {
(: , : ) => {
(!request.) {
reply.().({ : });
}
resource = (request);
(!resource) {
reply.().({ : });
}
user = fastify...({
: { : request.. },
});
(!user) {
reply.().({ : });
}
allowed = (action, {
user,
resource,
: {
: (),
: request.,
},
});
(!allowed) {
reply.().({ : });
}
(request & { : T }). = resource;
};
}
);
};
(abacPlugin);
Using ABAC
import { FastifyPluginAsync } from 'fastify';
const postsRoutes: FastifyPluginAsync = async (fastify) => {
fastify.put<{ Params: { id: string } }>('/:id', {
preHandler: [
fastify.authenticate,
fastify.checkAccess('post:update', async (request) => {
const { id } = request.params as { id: string };
return fastify.db.post.findUnique({ where: { id } });
}),
],
}, async (request) => {
const post = (request as unknown as { resource: Post }).resource;
const { title, content } = request.body as { title: string; content: string };
return fastify.db.post.update({
where: { id: post. },
: { title, content },
});
});
fastify.<{ : { : } }>(, {
: [
fastify.,
fastify.(, (request) => {
{ id } = request. { : };
fastify...({ : { id } });
}),
],
}, (request) => {
post = (request { : }).;
fastify...({ : { : post. } });
{ : };
});
};
postsRoutes;
Scope-Based Authorization
export const Scope = {
READ: 'read',
WRITE: 'write',
DELETE: 'delete',
ADMIN: 'admin',
} as const;
export type Scope = (typeof Scope)[keyof typeof Scope];
export function parseScopes(scopeString: string): Scope[] {
return scopeString.split(' ').filter((s): s is Scope =>
Object.values(Scope).includes(s as Scope)
);
}
export function hasScope(userScopes: Scope[], requiredScope: Scope): boolean {
if (userScopes.includes(Scope.ADMIN)) return true;
userScopes.(requiredScope);
}
Best Practices
- Default deny - Require explicit permission grants
- Centralize policies - Single source of truth
- Audit access - Log authorization decisions
- Fail closed - Errors result in denial
- Test policies - Unit test authorization logic
- Separate concerns - Authentication vs authorization
Notes
- RBAC is simpler for most applications
- ABAC is more flexible for complex rules
- Combine both for best results
- Cache permission checks for performance