Claude-skill-registry authorization-patterns
Authorization patterns including RBAC and ABAC. Use when implementing access control.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/authorization-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-authorization-patterns && rm -rf "$T"
manifest:
skills/data/authorization-patterns/SKILL.mdsource content
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
// src/lib/roles.ts export const Role = { USER: 'USER', MODERATOR: 'MODERATOR', ADMIN: 'ADMIN', } as const; export type Role = (typeof Role)[keyof typeof Role]; export const Permission = { // Users USER_READ: 'user:read', USER_CREATE: 'user:create', USER_UPDATE: 'user:update', USER_DELETE: 'user:delete', // Posts POST_READ: 'post:read', POST_CREATE: 'post:create', POST_UPDATE: 'post:update', POST_DELETE: 'post:delete', // Admin 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
// src/lib/permissions.ts 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
// src/plugins/authorize.ts 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) => { // Require ALL permissions fastify.decorate('authorize', (...permissions: Permission[]) => { return async (request: FastifyRequest, reply: FastifyReply) => { if (!request.user) { return reply.status(401).send({ error: 'Unauthorized' }); } const authorized = permissions.every((p) => hasPermission(request.user.role, p) ); if (!authorized) { return reply.status(403).send({ error: 'Forbidden' }); } }; }); // Require ANY permission fastify.decorate('authorizeAny', (...permissions: Permission[]) => { return async (request: FastifyRequest, reply: FastifyReply) => { if (!request.user) { return reply.status(401).send({ error: 'Unauthorized' }); } const authorized = hasAnyPermission(request.user.role, permissions); if (!authorized) { return reply.status(403).send({ error: 'Forbidden' }); } }; }); }; export default fp(authorizePlugin);
Using Authorization
// src/routes/admin.ts import { FastifyPluginAsync } from 'fastify'; import { Permission } from '../lib/roles'; const adminRoutes: FastifyPluginAsync = async (fastify) => { // All routes require authentication fastify.addHook('preHandler', fastify.authenticate); // Admin dashboard - requires admin access fastify.get('/dashboard', { preHandler: [fastify.authorize(Permission.ADMIN_ACCESS)], }, async () => { return { stats: await getAdminStats(fastify) }; }); // Manage users - requires user management permissions 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(Permission.USER_DELETE)], }, async (request) => { const { id } = request.params as { id: string }; await fastify.db.user.delete({ where: { id } }); return { success: true }; }); }; export default adminRoutes;
Attribute-Based Access Control (ABAC)
Policy Definition
// src/lib/abac/policies.ts 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 policies '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': ({ user, resource }) => { const post = resource as Post; return post.authorId === user.id || user.role === 'ADMIN'; }, // User policies 'user:update': ({ user, resource }) => { const targetUser = resource as User; return user.id === targetUser.id || user.role === 'ADMIN'; }, // Time-based policy 'admin:access': ({ user, environment }) => { if (user.role !== 'ADMIN') return false; // Only during business hours const hour = environment?.time?.getHours() ?? new Date().getHours(); return hour >= 9 && hour <= 17; }, }; export function checkPolicy( action: string, context: Omit<PolicyContext, 'action'> ): boolean { const policy = policies[action]; if (!policy) { return false; // Deny by default } return policy({ ...context, action }); }
ABAC Middleware
// src/plugins/abac.ts 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>) => { return async (request: FastifyRequest, reply: FastifyReply) => { if (!request.user) { return reply.status(401).send({ error: 'Unauthorized' }); } const resource = await loadResource(request); if (!resource) { return reply.status(404).send({ error: 'Resource not found' }); } const user = await fastify.db.user.findUnique({ where: { id: request.user.userId }, }); if (!user) { return reply.status(401).send({ error: 'Unauthorized' }); } const allowed = checkPolicy(action, { user, resource, environment: { time: new Date(), ip: request.ip, }, }); if (!allowed) { return reply.status(403).send({ error: 'Forbidden' }); } // Attach resource to request for handler (request as FastifyRequest & { resource: T }).resource = resource; }; } ); }; export default fp(abacPlugin);
Using ABAC
// src/routes/posts.ts import { FastifyPluginAsync } from 'fastify'; const postsRoutes: FastifyPluginAsync = async (fastify) => { // Update post with ABAC 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.id }, data: { title, content }, }); }); // Delete post with ABAC fastify.delete<{ Params: { id: string } }>('/:id', { preHandler: [ fastify.authenticate, fastify.checkAccess('post:delete', 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; await fastify.db.post.delete({ where: { id: post.id } }); return { success: true }; }); }; export default postsRoutes;
Scope-Based Authorization
// src/lib/scopes.ts 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; return userScopes.includes(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