Agent skill
prisma-rbac
Add, repair, and verify Prisma-backed RBAC (User, Group, Role, Permission) in existing NestJS backends with JWT access tokens, refresh-token session rotation, permission guards, and CRUD/assignment APIs. Use when users ask to implement or fix role-based access control, auth session rotation, permission-protected endpoints, or Prisma auth/authorization wiring.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/nestjs-prisma-rbac-setup
SKILL.md
RBAC with Prisma
Use this skill to implement or remediate Role-Based Access Control in a NestJS backend that already uses Prisma. The target model is:
User -> UserGroup -> Group -> GroupRole -> Role -> RolePermission -> Permission
This skill assumes a synchronous API workflow and session-backed refresh token rotation.
Workflow
Step 1: Preflight checks (required)
- Prisma and PrismaService/PrismaModule are present.
- ConfigModule is available (for
JWT_SECRET). - Optional:
@nestjs/jwt,@nestjs/config, bcrypt/bcryptjs (or project password hashing), Swagger,crypto(Node built-in) for secure random refresh tokens.
If any optional dependency is missing, add it or adapt (for example, use Nest LoggerService instead of a custom logger).
Stop and request the correct backend path if this is not an existing NestJS + Prisma project.
Step 2: Enforce Prisma schema baseline (required)
Add RBAC models to prisma/schema.prisma. Full schema block is in reference.md.
Models:
- User:
id,username(unique),password,createdAt,updatedAt; relationuserGroups UserGroup[]. - Group:
id,name(unique),description?, timestamps; relationsusers UserGroup[],groupRoles GroupRole[]. - UserGroup: composite
@@id([userId, groupId]); relations to User and Group withonDelete: Cascade;@@map("user_groups"). - Permission:
id,name(unique),description?, timestamps; relationrolePermissions RolePermission[]. - Role:
id,name(unique),description?, timestamps; relationsgroupRoles GroupRole[],rolePermissions RolePermission[]. - RolePermission: composite
@@id([roleId, permissionId]); relations to Role and Permission withonDelete: Cascade;@@map("role_permissions"). - GroupRole: composite
@@id([groupId, roleId]); relations to Group and Role withonDelete: Cascade;@@map("group_roles").
For refresh tokens, add a session model in Prisma (recommended for production):
- AuthSession:
id(UUID/cuid),userId,refreshTokenHash,expiresAt,revokedAt?,replacedById?,userAgent?,ip?,createdAt,updatedAt. - Indexes:
@@index([userId]),@@index([expiresAt]),@@index([revokedAt]), and uniquerefreshTokenHashwhen feasible.
Do not store raw refresh tokens in Prisma; store only a one-way hash.
After schema changes, run npx prisma migrate dev and npx prisma generate.
Step 3: Enforce token/session model (required)
Use a two-token model:
- Access token (JWT): short TTL (5-15 minutes), includes
suband optionalusername; used for API authorization. - Refresh token: long TTL (7-30 days), opaque random string (recommended) or JWT with
jti; used only at refresh endpoint.
Standardized refresh behavior:
POST /auth/loginreturnsaccessTokenandrefreshTokenand creates a DB session with hashed refresh token.POST /auth/refreshvalidates refresh token, session status (revokedAtis null,expiresAtis in the future), and user existence.- On success, rotate refresh token:
- Mark old session as revoked and optionally set
replacedById. - Create new session row with new
refreshTokenHashand new expiry. - Return new
accessTokenand newrefreshToken.
- Mark old session as revoked and optionally set
- If a revoked or unknown refresh token is presented, treat as token reuse and revoke all active sessions for that user or device scope.
POST /auth/logoutrevokes current session.- Optional
POST /auth/logout-allrevokes all user sessions.
Transport standard:
- Prefer HttpOnly + Secure + SameSite cookie for refresh tokens.
- Accept header/body refresh token only for non-browser clients; do not log tokens.
- Keep access token in
Authorization: Bearer <token>.
Step 4: Wire module boundaries and DI (required)
Single RbacModule (for example, src/libs/rbac/rbac.module.ts):
@Global()so services are available app-wide.- Imports:
ConfigModule,JwtModule.registerAsync({ useFactory: (config: ConfigService) => ({ secret: config.get('JWT_SECRET') }), inject: [ConfigService] }). Throw ifJWT_SECRETis missing. - Providers: AuthService, SessionService (or TokenService backed by Prisma), UserService, GroupService, RoleService, PermissionService, UserGroupsService (and optionally GroupRolesService, RolePermissionsService if mirroring full reference).
- Exports: same services.
- Controllers: AuthController, UserController, GroupController, RoleController, PermissionController.
Register RbacModule in AppModule imports.
Step 5: Implement guard behavior (required)
File: src/libs/rbac/guards/auth.guard.ts.
- Extract token:
AuthContextUtils.getTokenFromHeader(request)fromAuthorization: Bearer <token>. If missing, throwUnauthorizedException. - Validate:
authService.isValidToken(token). If false, throw Unauthorized. - Session check is optional for access tokens if stateless-only, recommended if session-aware access revocation is required:
- If session-aware: include a
sidclaim in access token and verify active session exists in DB/cache. - If missing or revoked, throw Unauthorized (for example, "Session not found or revoked").
- If session-aware: include a
- Attach token to request (for example,
(request as AuthenticatedRequest).authToken = token). - Permission check: Read
permissionsmetadata from handler and class:reflector.get<string[]>('permissions', context.getHandler())then class. IfrequiredPermissions.length > 0:- Decode JWT to get
sub(userId). - Run Prisma: count permissions where
nameis inrequiredPermissionsand the permission is reachable via: User (userId) -> UserGroup -> Group -> GroupRole -> Role -> RolePermission -> Permission. Use a singleprisma.permission.count({ where: { name: { in: requiredPermissions }, rolePermissions: { some: { role: { groupRoles: { some: { group: { users: { some: { userId } } } } } } } } } }). - If count < requiredPermissions.length, throw Unauthorized ("Insufficient permissions").
- Decode JWT to get
- Return true.
Inject: AuthService, SessionService (or TokenService), Reflector, JwtService, PrismaService. Use Nest LoggerService or project logger for warnings.
Step 6: Implement auth/session services (required)
- login(username, password): Validate with UserService (for example,
userService.isValidUser), fetch user, generate short-lived access JWT (payload:{ sub: user.id, username, sid }), generate refresh token, hash refresh token, persist session, return{ accessToken, refreshToken }. Throw Unauthorized on invalid credentials. - refresh(refreshToken): Hash and lookup session; verify not revoked and not expired; rotate session and refresh token; return new token pair.
- logout(refreshToken | sid): Revoke current session.
- logoutAll(userId): Revoke all active sessions for user (optional but recommended).
- generateToken(payload): Use JwtService with secret from ConfigService.
- isValidToken(token): JwtService.verifyAsync with secret; ensure payload has
suband (if used)username. Return boolean.
SessionService (or TokenService)
Prefer Prisma-backed session store over in-memory for production.
- createSession(input): Persist hashed refresh token, expiry, metadata.
- findActiveByRefreshTokenHash(hash): Return matching active session.
- rotateSession(sessionId, newHash, newExpiry): Revoke old + create successor session atomically.
- revokeSession(sessionId): Mark revoked.
- revokeAllForUser(userId): Revoke all active sessions for user.
- cleanupExpired(): Optional scheduled cleanup.
If using Redis instead of Prisma, keep the same service contract and rotation semantics.
Step 7: Add auth context helpers and interfaces (required)
- AuthContextUtils (for example,
src/libs/rbac/utils/auth-context.utils.ts): ParseAuthorizationheader; staticgetTokenFromHeader(request)returns token when scheme isBearer, else undefined. - SessionInterface (for example,
src/libs/rbac/interfaces/session.interface.ts): includeid,userId,refreshTokenHash,expiresAt,revokedAt?,replacedById?.
Step 8: Protect routes with permissions metadata (required)
- Guard + permissions:
@UseGuards(AuthGuard)and@SetMetadata('permissions', ['resource.action'])(for example,['users.read'],['users.create']). Guard requires user to have all listed permissions. - Composite (optional): When Swagger is used, define
Authentication()=applyDecorators(UseGuards(AuthGuard), ApiBearerAuth('JWT-auth'), HttpCode(HttpStatus.OK))and use@Authentication()on protected routes.
Step 9: Align DTOs and controllers (required)
DTOs: Create/patch/get/delete per entity. Examples:
- CreateUserDto: username, password (and validation).
- GetUserDto: id, username, createdAt (no password).
- PatchUserDto: optional username, password.
- Replace-user-groups: body as array of group IDs; same idea for group-roles and role-permissions.
Use class-validator where applicable (IsString, IsOptional, MinLength, etc.). If Swagger is present, add @ApiProperty to DTOs.
Controllers:
- Auth:
POST login(body: username, password; return token pair),POST refresh(read refresh token from cookie/header/body, rotate session),POST logout(revoke current refresh session), optionalPOST logout-all. - Users: CRUD (list, create, get by id, patch, delete), change-password, list/replace/assign/remove user groups. Protect with AuthGuard and
@SetMetadata('permissions', ['users.read'])etc. - Groups, Roles, Permissions: CRUD plus assignment endpoints (for example, replace group roles, replace role permissions). Same guard + permissions pattern.
Use a consistent permission naming convention (for example, users.read, users.create, users.update, users.delete, groups.assign).
Step 10: Enforce hashing and secret-safe logging (required)
Do not store plain passwords. Use bcrypt/bcryptjs (for example, per setup-bcryptjs-nestjs skill) or the project's existing hashing. UserService (or a dedicated service) should hash on create/patch and verify on login.
Adaptations
- Logger: Reference project may use a custom logger; use Nest
LoggerServiceor the project's logger in AuthGuard and AuthService. - Swagger: If present, add
@ApiBearerAuth('JWT-auth'),@ApiOperation,@ApiResponseon auth and protected endpoints; optionalResponses(kind)decorator for common status codes. Document refresh cookie/header contract explicitly. If Swagger is not used, omit these. - Encryption package: If the reference uses a specific encryption package, prefer bcrypt/bcryptjs for new backends unless the project already has a standard.
- Refresh token hashing: Prefer SHA-256/SHA-512 with per-token random value, or HMAC keyed by server secret; never store plaintext refresh tokens.
Step 11: Verification gates (required)
- Build/test passes for the backend after RBAC changes.
- Prisma migration and client generation succeed.
- Login returns access+refresh token pair.
- Refresh rotates sessions and invalidates/revokes replaced refresh sessions.
- Protected routes enforce required permissions and deny insufficient scopes.
- No plaintext passwords or refresh tokens are persisted or logged.
Checklist
- Prisma schema has User, Group, Role, Permission, UserGroup, GroupRole, RolePermission; migrations run.
- AuthSession (or equivalent session store) exists for hashed refresh tokens and rotation.
- JWT_SECRET in env; JwtModule registered in RbacModule.
- Access token TTL and refresh token TTL configured (env-driven).
- AuthService, SessionService (or TokenService), UserService, GroupService, RoleService, PermissionService, UserGroupsService (and optional GroupRolesService, RolePermissionsService) implemented.
- AuthGuard: token extraction, validation, permission count via Prisma, and optional session-aware access-token revocation.
- Refresh endpoint rotates refresh token on every use and handles reuse detection.
- AuthContextUtils.getTokenFromHeader and SessionInterface in place.
- AuthController (login, refresh, logout, optional logout-all); User, Group, Role, Permission controllers with CRUD and assignments; routes protected with AuthGuard and SetMetadata('permissions', [...]).
- Passwords hashed; no plain-text storage.
- Refresh tokens are hashed, never logged, and delivered via secure transport.
For full schema details, see reference.md.
Official References
- NestJS Authentication: https://docs.nestjs.com/security/authentication
- NestJS Authorization: https://docs.nestjs.com/security/authorization
- NestJS Guards: https://docs.nestjs.com/guards
- Prisma Data Model: https://www.prisma.io/docs/orm/prisma-schema/data-model/models
- Prisma Migrate: https://www.prisma.io/docs/orm/prisma-migrate
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?