home / skills / meriley / claude-code-skills / implementing-casbin

implementing-casbin skill

/skills/implementing-casbin

npx playbooks add skill meriley/claude-code-skills --skill implementing-casbin

Review the files below or copy the command above to add this skill to your agents.

Files (3)
SKILL.md
7.3 KB
---
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