Agent Skills: Implementing Casbin Authorization

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.

UncategorizedID: meriley/claude-code-skills/implementing-casbin

Install this agent skill to your local

pnpm dlx add-skill https://github.com/meriley/claude-code-skills/tree/HEAD/skills/implementing-casbin

Skill Files

Browse the full folder contents for implementing-casbin.

Download Skill

Loading file tree…

skills/implementing-casbin/SKILL.md

Skill Metadata

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.

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

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:

[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

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

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

// 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)

[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

[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:

// 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)

[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:

e.AddRoleForUserInDomain("alice", "admin", "tenant1")
e.AddPolicy("admin", "tenant1", "/api/*", "GET")
allowed, _ := e.Enforce("alice", "tenant1", "/api/users", "GET")  // true

GORM Adapter Setup

PostgreSQL

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

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

sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(10)
sqlDB.SetMaxOpenConns(100)
sqlDB.SetConnMaxLifetime(time.Hour)

Policy Management

Adding Policies

e.AddPolicy("alice", "/api/users", "GET")           // Direct permission
e.AddRoleForUser("alice", "admin")                  // Assign role
e.AddRolesForUser("bob", []string{"viewer", "editor"})  // Multiple roles

Removing Policies

e.RemovePolicy("alice", "/api/users", "GET")
e.RemoveFilteredPolicy(0, "alice")    // All policies for subject
e.DeleteRoleForUser("alice", "admin")

Querying Policies

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

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

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

// 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
  • GORM Adapter
  • See REFERENCE.md for complete API reference and built-in functions
  • See TEMPLATES.md for copy-paste middleware and test templates