implementing-casbin
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/:idpatterns
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/:idpatternskeyMatch3:/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
More from meriley/claude-code-skills
vendure-developing
Develop Vendure e-commerce plugins, extend GraphQL APIs, create Admin UI components, and define database entities. Use vendure-expert agent for comprehensive guidance across all Vendure development domains.
36vendure-admin-ui-writing
Create Vendure Admin UI extensions with React components, route registration, navigation menus, and GraphQL integration. Handles useQuery, useMutation, useInjector patterns. Use when building Admin UI features for Vendure plugins.
33vendure-graphql-reviewing
Review Vendure GraphQL resolvers for missing RequestContext, improper permissions, DTO violations, and schema extension issues. Use when reviewing GraphQL PRs or auditing API quality.
21quality-check
⚠️ MANDATORY - Automatically invoked by safe-commit. Runs language-specific linting, formatting, static analysis, and type checking. Treats linter issues as build failures that MUST be fixed before commit. Auto-fixes when possible. NEVER run linters manually.
20obs-audio-plugin-writing
Create OBS Studio audio plugins including audio sources, audio filters, and real-time audio processing. Covers obs_source_info for audio, filter_audio callback, audio data structures, settings API, and properties UI. Use when developing audio plugins for OBS.
10run-tests
⚠️ MANDATORY - Automatically invoked by safe-commit. Executes comprehensive testing suite including unit tests (minimum 90% coverage), integration tests, and E2E tests (100% pass required). Reports coverage and failures. MUST pass before commit. NEVER run tests manually before commit.
7