Back to skills
SKILL.md
Webflow Enterprise Rbac
ASecurity"Configure Webflow enterprise access control \u2014 OAuth 2.0 app authorization,\n\
- 2 stars
- 0 votes
- 0 copies
- 0 views
- Added November 11, 2026
Works with
Security analysis
100/100npx -y skills add pauldx/claudifying --skill webflow-enterprise-rbac --agent claude-codeAre you the author of Webflow Enterprise Rbac?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/pauldx-webflow-enterprise-rbac)---
name: webflow-enterprise-rbac
description: "Configure Webflow enterprise access control \u2014 OAuth 2.0 app authorization,\n\
scope-based RBAC, per-site token isolation, workspace member management,\nand audit\
\ logging for compliance.\nTrigger with phrases like \"webflow RBAC\", \"webflow\
\ enterprise\",\n\"webflow roles\", \"webflow permissions\", \"webflow OAuth scopes\"\
,\n\"webflow access control\", \"webflow workspace members\".\n"
allowed-tools: Read, Write, Edit
version: 1.0.0
license: MIT
tags:
- saas
- design
- no-code
- webflow
compatibility: Designed for Claude Code
---
# Webflow Enterprise RBAC
## Overview
Enterprise-grade access control for Webflow Data API v2 integrations. Uses Webflow's
OAuth 2.0 scope system, per-site token isolation, and application-level RBAC to
enforce least privilege across teams and environments.
## Prerequisites
- Webflow workspace (Core plan or higher for multiple members)
- OAuth 2.0 Data Client App (for user-authorized access)
- Understanding of Webflow scopes
## Webflow's Native Access Model
Webflow provides three levels of access control:
| Level | Mechanism | Granularity |
|-------|-----------|-------------|
| **Workspace tokens** | API token | All sites in workspace |
| **Site tokens** | API token | Single site |
| **OAuth tokens** | OAuth 2.0 authorization | User-authorized scopes per site |
## Scope-Based Permission Model
Webflow scopes map directly to API access:
| Scope | Read Access | Write Access |
|-------|-------------|--------------|
| `sites:read` | List/get sites | — |
| `sites:write` | — | Publish sites |
| `cms:read` | List collections, read items | — |
| `cms:write` | — | Create/update/delete CMS items |
| `pages:read` | List/get pages | — |
| `pages:write` | — | Update page settings |
| `forms:read` | List forms, read submissions | — |
| `ecommerce:read` | List products, orders, inventory | — |
| `ecommerce:write` | — | Create products, fulfill orders |
| `custom_code:read` | List registered scripts | — |
| `custom_code:write` | — | Register/apply custom code |
## Instructions
### Step 1: Define Application Roles
Map your application roles to Webflow scope sets:
```typescript
enum AppRole {
ContentViewer = "content_viewer",
ContentEditor = "content_editor",
SiteAdmin = "site_admin",
EcommerceManager = "ecommerce_manager",
FormProcessor = "form_processor",
}
// Map roles to required Webflow OAuth scopes
const ROLE_SCOPES: Record<AppRole, string[]> = {
[AppRole.ContentViewer]: ["sites:read", "cms:read", "pages:read"],
[AppRole.ContentEditor]: ["sites:read", "cms:read", "cms:write", "pages:read"],
[AppRole.SiteAdmin]: [
"sites:read", "sites:write",
"cms:read", "cms:write",
"pages:read", "pages:write",
"custom_code:read", "custom_code:write",
],
[AppRole.EcommerceManager]: [
"sites:read",
"ecommerce:read", "ecommerce:write",
"cms:read", // Products are CMS collections
],
[AppRole.FormProcessor]: ["sites:read", "forms:read"],
};
```
### Step 2: OAuth App with Role-Based Scopes
Request only the scopes needed for the user's role:
```typescript
function getAuthorizationUrl(role: AppRole, state: string): string {
const scopes = ROLE_SCOPES[role];
const scopeString = scopes.join(" ");
return (
`https://webflow.com/oauth/authorize` +
`?client_id=${process.env.WEBFLOW_CLIENT_ID}` +
`&response_type=code` +
`&redirect_uri=${encodeURIComponent(process.env.REDIRECT_URI!)}` +
`&scope=${encodeURIComponent(scopeString)}` +
`&state=${state}` // CSRF protection
);
}
// Initiate OAuth flow with role-appropriate scopes
app.get("/auth/webflow", (req, res) => {
const role = req.user.appRole as AppRole;
const state = generateCsrfToken(req.session.id);
res.redirect(getAuthorizationUrl(role, state));
});
```
### Step 3: Token Storage with Role Metadata
```typescript
interface StoredToken {
accessToken: string;
userId: string;
role: AppRole;
scopes: string[];
siteIds: string[]; // Which sites this token can access
createdAt: Date;
lastUsed: Date;
}
async function storeToken(token: StoredToken): Promise<void> {
// Encrypt token before storing
const encrypted = encrypt(token.accessToken);
await db.webflowTokens.upsert({
where: { userId: token.userId },
create: {
...token,
accessToken: encrypted,
},
update: {
accessToken: encrypted,
lastUsed: new Date(),
},
});
}
```
### Step 4: Per-Site Token Isolation
Use site-scoped tokens to limit blast radius:
```typescript
class SiteIsolatedClient {
private clients = new Map<string, WebflowClient>();
// Each site gets its own token — compromise of one doesn't affect others
registerSite(siteId: string, siteToken: string): void {
this.clients.set(siteId, new WebflowClient({ accessToken: siteToken }));
}
getClient(siteId: string): WebflowClient {
const client = this.clients.get(siteId);
if (!client) {
throw new Error(`No token registered for site ${siteId}`);
}
return client;
}
// Rotate a single site's token without affecting others
rotateToken(siteId: string, newToken: string): void {
this.clients.set(siteId, new WebflowClient({ accessToken: newToken }));
console.log(`Token rotated for site ${siteId}`);
}
}
const siteClients = new SiteIsolatedClient();
siteClients.registerSite("site-prod", process.env.WEBFLOW_TOKEN_PROD!);
siteClients.registerSite("site-staging", process.env.WEBFLOW_TOKEN_STAGING!);
```
### Step 5: Permission Check Middleware
```typescript
import { Request, Response, NextFunction } from "express";
function requireWebflowScope(requiredScopes: string[]) {
return async (req: Request, res: Response, next: NextFunction) => {
const userToken = await db.webflowTokens.findByUserId(req.user.id);
if (!userToken) {
return res.status(401).json({ error: "No Webflow authorization" });
}
// Check if user's token has the required scopes
const missingScopes = requiredScopes.filter(
s => !userToken.scopes.includes(s)
);
if (missingScopes.length > 0) {
return res.status(403).json({
error: "Insufficient Webflow permissions",
missing: missingScopes,
userRole: userToken.role,
hint: `Role "${userToken.role}" does not include: ${missingScopes.join(", ")}`,
});
}
next();
};
}
// Usage
app.post(
"/api/cms/items",
requireWebflowScope(["cms:write"]),
createItemHandler
);
app.get(
"/api/forms/submissions",
requireWebflowScope(["forms:read"]),
listSubmissionsHandler
);
app.post(
"/api/site/publish",
requireWebflowScope(["sites:write"]),
publishSiteHandler
);
```
### Step 6: Audit Logging
```typescript
interface WebflowAuditEntry {
timestamp: Date;
userId: string;
role: AppRole;
operation: string;
siteId: string;
resourceType: string;
resourceId?: string;
result: "success" | "denied" | "error";
scopes: string[];
ipAddress: string;
}
async function auditWebflowAccess(entry: WebflowAuditEntry): Promise<void> {
// Write to audit log (never delete audit entries)
await db.auditLog.create({
data: {
...entry,
timestamp: entry.timestamp.toISOString(),
},
});
// Alert on suspicious patterns
if (entry.result === "denied") {
console.warn(`ACCESS DENIED: ${entry.userId} attempted ${entry.operation} on ${entry.resourceType}`);
}
// Alert on admin operations
if (entry.role === AppRole.SiteAdmin && entry.operation.includes("publish")) {
console.log(`ADMIN ACTION: ${entry.userId} published site ${entry.siteId}`);
}
}
// Wrap API calls with audit logging
async function auditedCall<T>(
entry: Omit<WebflowAuditEntry, "timestamp" | "result">,
operation: () => Promise<T>
): Promise<T> {
try {
const result = await operation();
await auditWebflowAccess({ ...entry, timestamp: new Date(), result: "success" });
return result;
} catch (error) {
await auditWebflowAccess({ ...entry, timestamp: new Date(), result: "error" });
throw error;
}
}
```
### Step 7: Token Rotation Schedule
```typescript
async function checkTokenAge(): Promise<void> {
const tokens = await db.webflowTokens.findMany();
for (const token of tokens) {
const ageInDays = (Date.now() - token.createdAt.getTime()) / (1000 * 60 * 60 * 24);
if (ageInDays > 90) {
console.warn(
`Token for user ${token.userId} (${token.role}) is ${Math.floor(ageInDays)} days old. ` +
`Rotation recommended.`
);
// Send notification to user/admin
}
}
}
// Run weekly
// cron: "0 9 * * 1"
```
## Output
- Role-to-scope mapping for Webflow OAuth
- OAuth authorization with role-appropriate scopes
- Per-site token isolation
- Permission check middleware for API endpoints
- Comprehensive audit logging
- Token age monitoring and rotation alerts
## Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| OAuth scope mismatch | App requests more scopes than configured | Match app settings in Webflow dashboard |
| 403 on API call | Token missing required scope | Re-authorize with correct role |
| Token not found | User never completed OAuth flow | Redirect to authorization |
| Audit gap | Error in async logging | Add fallback logging to console |
## Resources
- [Webflow OAuth Reference](https://developers.webflow.com/data/reference/oauth-app)
- [Webflow Scopes](https://developers.webflow.com/data/reference/scopes)
- [Webflow Authentication](https://developers.webflow.com/data/reference/authentication)
## Next Steps
For major migrations, see `webflow-migration-deep-dive`.
Attribution
Related skills
View all in development →Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.