From saaskit
Implements server-side RBAC and permission checks by validating and decoding Scalekit access tokens, extracting roles/permissions, and enforcing them with middleware/decorators at route boundaries. Use when adding role-based access control, protecting routes or endpoints, building auth middleware, or checking JWT permissions with Scalekit tokens.
How this skill is triggered — by the user, by Claude, or both
Slash command
/saaskit:implementing-access-controlThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
After authentication is working and the app must authorize access to routes/actions by inspecting the user's access token for `roles` and `permissions`.
After authentication is working and the app must authorize access to routes/actions by inspecting the user's access token for roles and permissions.
sub, oid, roles, and permissions.req.user = { id, organizationId, roles, permissions }) so downstream handlers can authorize consistently.resource:action).Validate+extract, then RBAC/PBAC guards.
// validate + extract
const validateAndExtractAuth = async (req, res, next) => {
try {
const accessToken = decrypt(req.cookies.accessToken); // if encrypted
const tokenData = await scalekit.validateAccessTokenAndGetClaims(accessToken);
if (!tokenData) return res.status(401).json({ error: "Unauthorized" });
req.user = {
id: tokenData.sub,
organizationId: tokenData.oid,
roles: tokenData.roles || [],
permissions: tokenData.permissions || []
};
next();
} catch {
return res.status(401).json({ error: "Authentication failed" });
}
};
// RBAC
const hasRole = (user, role) => user.roles?.includes(role);
const requireRole = (role) => (req, res, next) =>
hasRole(req.user, role) ? next() : res.status(403).json({ error: `Access denied. Required role: ${role}` });
// PBAC
const hasPermission = (user, perm) => user.permissions?.includes(perm);
const requirePermission = (perm) => (req, res, next) =>
hasPermission(req.user, perm) ? next() : res.status(403).json({ error: `Access denied. Required permission: ${perm}` });
// usage
app.get("/api/projects", validateAndExtractAuth, requirePermission("projects:read"), handler);
app.get("/api/admin/users", validateAndExtractAuth, requireRole("admin"), handler);
Validate+extract, then RBAC/PBAC decorators.
from functools import wraps
def validate_and_extract_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
access_token = decrypt(request.cookies.get("accessToken"))
try:
token_data = scalekit_client.validate_access_token_and_get_claims(access_token)
except Exception:
return jsonify({"error": "Invalid or expired token"}), 401
request.user = {
"id": token_data.get("sub"),
"organization_id": token_data.get("oid"),
"roles": token_data.get("roles", []),
"permissions": token_data.get("permissions", []),
}
return f(*args, **kwargs)
return decorated
def require_role(role):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if role not in getattr(request, "user", {}).get("roles", []):
return jsonify({"error": f"Access denied. Required role: {role}"}), 403
return f(*args, **kwargs)
return decorated
return decorator
def require_permission(permission):
def decorator(f):
@wraps(f)
def decorated(*args, **kwargs):
if permission not in getattr(request, "user", {}).get("permissions", []):
return jsonify({"error": f"Access denied. Required permission: {permission}"}), 403
return f(*args, **kwargs)
return decorated
return decorator
After implementing, test these cases:
# Test with a valid token that has the required role
curl -H "Cookie: accessToken=<valid_admin_token>" http://localhost:3000/api/admin/users
# Expected: 200
# Test with a token missing the required role
curl -H "Cookie: accessToken=<valid_member_token>" http://localhost:3000/api/admin/users
# Expected: 403 {"error": "Access denied. Required role: admin"}
# Test with an expired/invalid token
curl -H "Cookie: accessToken=expired_token" http://localhost:3000/api/projects
# Expected: 401 {"error": "Invalid or expired token"}
If 403 isn't returned for unauthorized users, check that the middleware chain order is correct: validateAndExtractAuth must run before requireRole/requirePermission.
projects:create, tasks:assign)roles and permissions normalized as arrays in request contextrequireRole(...) and/or requirePermission(...) at the boundaryresource:action conventionnpx claudepluginhub scalekit-inc/authstack --plugin saaskitBlocks Edit/Write/Bash actions until Claude investigates importers, data schemas, and user instructions. Improves output quality by forcing concrete facts before edits.