home / skills / meriley / claude-code-skills / implementing-casbin
npx playbooks add skill meriley/claude-code-skills --skill implementing-casbinReview the files below or copy the command above to add this skill to your agents.
---
name: implementing-casbin
description: Implement role-based (RBAC) and attribute-based (ABAC) access control in Go using Casbin. Covers model configuration, GORM adapters, Chi/gRPC middleware, and production patterns. Use when implementing authorization in Go services.
version: 1.0.0
---
# Implementing Casbin Authorization
## Purpose
Implement fine-grained authorization in Go services using Casbin's policy-based access control. Supports RBAC (role-based), ABAC (attribute-based), and hybrid models with database-backed policy storage.
## When NOT to Use
- Simple API key authentication (use middleware directly)
- Single-user applications (no authorization needed)
- OAuth/OIDC token validation only (use dedicated auth libraries)
- Static, compile-time permissions (use Go interfaces/types)
---
## Quick Start Workflow
### Step 1: Install Dependencies
```bash
go get github.com/casbin/casbin/v2
go get github.com/casbin/gorm-adapter/v3
go get gorm.io/driver/postgres # or mysql
```
### Step 2: Create Model File
Create `config/rbac_model.conf`:
```ini
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
```
### Step 3: Initialize Enforcer with GORM
```go
package authz
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func NewEnforcer(dsn, modelPath string) (*casbin.Enforcer, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("connect to database: %w", err)
}
adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
return nil, fmt.Errorf("create adapter: %w", err)
}
e, err := casbin.NewEnforcer(modelPath, adapter)
if err != nil {
return nil, fmt.Errorf("create enforcer: %w", err)
}
e.EnableAutoSave(true)
return e, nil
}
```
### Step 4: Add Chi Middleware
```go
func Authorize(e *casbin.Enforcer) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := r.Context().Value("userID").(string)
if !ok || user == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
allowed, err := e.Enforce(user, r.URL.Path, r.Method)
if err != nil {
http.Error(w, "Authorization error", http.StatusInternalServerError)
return
}
if !allowed {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
```
### Step 5: Define Policies
```go
// Add role
e.AddRoleForUser("alice", "admin")
// Add permissions for role
e.AddPolicy("admin", "/api/users/*", "GET")
e.AddPolicy("admin", "/api/users/*", "POST")
// Check permission
allowed, _ := e.Enforce("alice", "/api/users/123", "GET") // true
```
---
## Model Configuration
### RBAC Model (Recommended Default)
```ini
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && r.act == p.act
```
**Key parts:**
- `g(r.sub, p.sub)`: Role inheritance (user -> role)
- `keyMatch2`: Supports `/path/:id` patterns
### ABAC Model
```ini
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj_rule, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && eval(p.obj_rule) && r.act == p.act
```
**Example:**
```go
// User can only access their own documents
e.AddPolicy("r.sub.ID == r.obj.OwnerID", "r.obj.Type == 'document'", "read")
```
### RBAC with Domains (Multi-Tenant)
```ini
[request_definition]
r = sub, dom, obj, act
[policy_definition]
p = sub, dom, obj, act
[role_definition]
g = _, _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && keyMatch2(r.obj, p.obj) && r.act == p.act
```
**Example:**
```go
e.AddRoleForUserInDomain("alice", "admin", "tenant1")
e.AddPolicy("admin", "tenant1", "/api/*", "GET")
allowed, _ := e.Enforce("alice", "tenant1", "/api/users", "GET") // true
```
---
## GORM Adapter Setup
### PostgreSQL
```go
dsn := "host=localhost user=app password=secret dbname=myapp port=5432 sslmode=disable"
db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
adapter, _ := gormadapter.NewAdapterByDB(db)
```
### MySQL
```go
dsn := "user:password@tcp(localhost:3306)/myapp?charset=utf8mb4&parseTime=True"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
adapter, _ := gormadapter.NewAdapterByDB(db)
```
### Connection Pooling
```go
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)
```
---
## Policy Management
### Adding Policies
```go
e.AddPolicy("alice", "/api/users", "GET") // Direct permission
e.AddRoleForUser("alice", "admin") // Assign role
e.AddRolesForUser("bob", []string{"viewer", "editor"}) // Multiple roles
```
### Removing Policies
```go
e.RemovePolicy("alice", "/api/users", "GET")
e.RemoveFilteredPolicy(0, "alice") // All policies for subject
e.DeleteRoleForUser("alice", "admin")
```
### Querying Policies
```go
policies, _ := e.GetPolicy() // All policies
roles, _ := e.GetRolesForUser("alice") // User's roles
permissions, _ := e.GetImplicitPermissionsForUser("alice") // Including inherited
hasRole, _ := e.HasRoleForUser("alice", "admin")
```
---
## Testing
### Unit Test Setup
```go
func TestEnforcer(t *testing.T) {
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
m, _ := model.NewModelFromString(modelText)
e, _ := casbin.NewEnforcer(m)
e.AddPolicy("admin", "/users", "GET")
e.AddRoleForUser("alice", "admin")
allowed, _ := e.Enforce("alice", "/users", "GET")
assert.True(t, allowed)
}
```
See TEMPLATES.md for complete test templates.
---
## Common Issues
### Policies not persisting
```go
e.EnableAutoSave(true) // Enable auto-save
// Or: e.SavePolicy() // Manual save
```
### keyMatch not working with path params
Use correct matcher function:
- `keyMatch2`: `/users/:id` patterns
- `keyMatch3`: `/users/{id}` patterns
### Slow performance with large policy sets
```go
// Load only relevant policies
filter := gormadapter.Filter{P: []string{"", "tenant1"}}
e.LoadFilteredPolicy(filter)
// Or batch enforcement
results, _ := e.BatchEnforce(requests)
```
---
## Integration with Other Skills
- **setup-go**: Run before Casbin setup
- **quality-check**: Lint authorization code
- **run-tests**: Execute authorization tests
---
## References
- [Casbin Documentation](https://casbin.org/docs/en/overview)
- [GORM Adapter](https://github.com/casbin/gorm-adapter)
- See **REFERENCE.md** for complete API reference and built-in functions
- See **TEMPLATES.md** for copy-paste middleware and test templates