Casbin
Casbin is a powerful and efficient open-source access control library for Golang projects. It provides support for enforcing authorization in various access control models like ACL, RBAC, ABAC, etc.
Installation
go get github.com/casbin/casbin/v2
For database adapters:
# GORM Adapter (PostgreSQL, MySQL, SQLite, SQL Server)
go get github.com/casbin/gorm-adapter/v3
# MongoDB Adapter
go get github.com/casbin/mongodb-adapter/v3
# Redis Adapter
go get github.com/casbin/redis-adapter/v2
Core Concepts
Policy Definition
Casbin uses a configuration file to define the access control model:
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) && r.obj == p.obj && r.act == p.act
Policy Data
policy.csv
p, alice, data1, read
p, alice, data2, write
p, bob, data2, write
g, alice, admin
Basic Usage
Simple RBAC Example
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
func main() {
// Initialize the enforcer with model and policy files
enforcer, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
log.Fatal("Failed to create enforcer:", err)
}
// Check permissions
result, err := enforcer.Enforce("alice", "data1", "read")
if err != nil {
log.Fatal("Error during enforcement:", err)
}
if result {
fmt.Println("Alice can read data1")
} else {
fmt.Println("Alice cannot read data1")
}
}
In-Memory Model and Policy
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
// Create model from string
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
`
m, err := model.NewModelFromString(modelText)
if err != nil {
log.Fatal("Failed to create model:", err)
}
// Create enforcer with in-memory adapter
enforcer, err := casbin.NewEnforcer(m, fileadapter.NewAdapter(""))
if err != nil {
log.Fatal("Failed to create enforcer:", err)
}
// Add policies programmatically
enforcer.AddPolicy("alice", "data1", "read")
enforcer.AddPolicy("bob", "data2", "write")
enforcer.AddPolicy("alice", "data2", "read")
// Check permissions
checkPermission(enforcer, "alice", "data1", "read")
checkPermission(enforcer, "alice", "data1", "write")
checkPermission(enforcer, "bob", "data2", "write")
}
func checkPermission(enforcer *casbin.Enforcer, sub, obj, act string) {
result, _ := enforcer.Enforce(sub, obj, act)
fmt.Printf("%s %s %s: %t\n", sub, act, obj, result)
}
Database Integration
GORM Adapter Example
package main
import (
"log"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v3"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func main() {
// Database connection
dsn := "host=localhost user=username password=password dbname=mydb port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// Initialize GORM adapter
adapter, err := gormadapter.NewAdapterByDB(db)
if err != nil {
log.Fatal("Failed to create adapter:", err)
}
// Create enforcer
enforcer, err := casbin.NewEnforcer("model.conf", adapter)
if err != nil {
log.Fatal("Failed to create enforcer:", err)
}
// Load policy from database
err = enforcer.LoadPolicy()
if err != nil {
log.Fatal("Failed to load policy:", err)
}
// Add policies
enforcer.AddPolicy("alice", "data1", "read")
enforcer.AddPolicy("bob", "data2", "write")
// Save policy to database
err = enforcer.SavePolicy()
if err != nil {
log.Fatal("Failed to save policy:", err)
}
// Check permissions
result, _ := enforcer.Enforce("alice", "data1", "read")
log.Printf("Alice can read data1: %t", result)
}
Web Framework Integration
Gin Middleware
package main
import (
"net/http"
"github.com/casbin/casbin/v2"
"github.com/gin-gonic/gin"
)
// CasbinMiddleware creates a Casbin middleware for Gin
func CasbinMiddleware(enforcer *casbin.Enforcer) gin.HandlerFunc {
return func(c *gin.Context) {
// Extract user from context (from JWT or session)
user, exists := c.Get("user")
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
return
}
// Extract resource and action from request
resource := c.Request.URL.Path
action := c.Request.Method
// Check permission
allowed, err := enforcer.Enforce(user.(string), resource, action)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Authorization check failed"})
return
}
if !allowed {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Access denied"})
return
}
c.Next()
}
}
func main() {
// Initialize Casbin enforcer
enforcer, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
panic(err)
}
r := gin.Default()
// Apply Casbin middleware
r.Use(CasbinMiddleware(enforcer))
r.GET("/api/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Users list"})
})
r.POST("/api/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "User created"})
})
r.Run(":8080")
}
Fiber Middleware
package main
import (
"github.com/casbin/casbin/v2"
"github.com/gofiber/fiber/v2"
)
func CasbinMiddleware(enforcer *casbin.Enforcer) fiber.Handler {
return func(c *fiber.Ctx) error {
// Extract user from context
user := c.Locals("user")
if user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Unauthorized",
})
}
// Extract resource and action
resource := c.Path()
action := c.Method()
// Check permission
allowed, err := enforcer.Enforce(user.(string), resource, action)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Authorization check failed",
})
}
if !allowed {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Access denied",
})
}
return c.Next()
}
}
func main() {
app := fiber.New()
// Initialize Casbin enforcer
enforcer, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
panic(err)
}
// Apply middleware
app.Use(CasbinMiddleware(enforcer))
app.Get("/api/users", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "Users list"})
})
app.Listen(":3000")
}
Advanced Features
Policy Management API
package main
import (
"fmt"
"log"
"github.com/casbin/casbin/v2"
)
type PolicyManager struct {
enforcer *casbin.Enforcer
}
func NewPolicyManager(enforcer *casbin.Enforcer) *PolicyManager {
return &PolicyManager{enforcer: enforcer}
}
// AddPolicy adds a new policy
func (pm *PolicyManager) AddPolicy(sub, obj, act string) error {
added, err := pm.enforcer.AddPolicy(sub, obj, act)
if err != nil {
return err
}
if !added {
return fmt.Errorf("policy already exists")
}
return pm.enforcer.SavePolicy()
}
// RemovePolicy removes a policy
func (pm *PolicyManager) RemovePolicy(sub, obj, act string) error {
removed, err := pm.enforcer.RemovePolicy(sub, obj, act)
if err != nil {
return err
}
if !removed {
return fmt.Errorf("policy not found")
}
return pm.enforcer.SavePolicy()
}
// AddRoleForUser assigns a role to a user
func (pm *PolicyManager) AddRoleForUser(user, role string) error {
added, err := pm.enforcer.AddRoleForUser(user, role)
if err != nil {
return err
}
if !added {
return fmt.Errorf("role assignment already exists")
}
return pm.enforcer.SavePolicy()
}
// GetRolesForUser gets all roles for a user
func (pm *PolicyManager) GetRolesForUser(user string) []string {
roles, _ := pm.enforcer.GetRolesForUser(user)
return roles
}
// GetUsersForRole gets all users with a specific role
func (pm *PolicyManager) GetUsersForRole(role string) []string {
users, _ := pm.enforcer.GetUsersForRole(role)
return users
}
func main() {
enforcer, err := casbin.NewEnforcer("model.conf", "policy.csv")
if err != nil {
log.Fatal(err)
}
pm := NewPolicyManager(enforcer)
// Add policies
pm.AddPolicy("alice", "data1", "read")
pm.AddRoleForUser("alice", "admin")
// Query policies
fmt.Println("Alice's roles:", pm.GetRolesForUser("alice"))
fmt.Println("Admin users:", pm.GetUsersForRole("admin"))
}
Custom Matchers
// Custom function for time-based access control
func init() {
casbin.AddFunction("timeMatch", func(args ...interface{}) (interface{}, error) {
currentHour := 14 // Current hour (2 PM)
allowedHours := args[0].(string)
// Parse allowed hours (e.g., "9-17" for 9 AM to 5 PM)
// Implementation would parse the string and check if current hour is within range
return currentHour >= 9 && currentHour <= 17, nil
})
}
// Model with custom matcher
modelText := `
[request_definition]
r = sub, obj, act, time
[policy_definition]
p = sub, obj, act, time
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act && timeMatch(p.time)
`
Best Practices
1. Model Organization
// Separate model configurations by environment
type CasbinConfig struct {
ModelPath string
PolicyPath string
Adapter persist.Adapter
}
func NewCasbinConfig(env string) *CasbinConfig {
switch env {
case "development":
return &CasbinConfig{
ModelPath: "configs/dev_model.conf",
PolicyPath: "configs/dev_policy.csv",
}
case "production":
adapter, _ := gormadapter.NewAdapter("postgres", "connection_string")
return &CasbinConfig{
ModelPath: "configs/prod_model.conf",
Adapter: adapter,
}
default:
return &CasbinConfig{
ModelPath: "configs/model.conf",
PolicyPath: "configs/policy.csv",
}
}
}
2. Error Handling
func SafeEnforce(enforcer *casbin.Enforcer, rvals ...interface{}) (bool, error) {
defer func() {
if r := recover(); r != nil {
log.Printf("Panic in Casbin enforcement: %v", r)
}
}()
result, err := enforcer.Enforce(rvals...)
if err != nil {
log.Printf("Casbin enforcement error: %v", err)
return false, err
}
return result, nil
}
3. Performance Optimization
// Use caching for better performance
func NewOptimizedEnforcer(modelPath string, adapter persist.Adapter) *casbin.Enforcer {
enforcer, _ := casbin.NewEnforcer(modelPath, adapter)
// Enable auto-save
enforcer.EnableAutoSave(true)
// Enable policy caching
enforcer.EnableEnforce(true)
return enforcer
}
Testing
Unit Tests
package auth
import (
"testing"
"github.com/casbin/casbin/v2"
"github.com/stretchr/testify/assert"
)
func TestCasbinEnforcement(t *testing.T) {
enforcer, err := casbin.NewEnforcer("testdata/model.conf", "testdata/policy.csv")
assert.NoError(t, err)
// Test cases
testCases := []struct {
user string
resource string
action string
expected bool
}{
{"alice", "data1", "read", true},
{"alice", "data1", "write", false},
{"bob", "data2", "write", true},
{"charlie", "data1", "read", false},
}
for _, tc := range testCases {
result, err := enforcer.Enforce(tc.user, tc.resource, tc.action)
assert.NoError(t, err)
assert.Equal(t, tc.expected, result,
"User %s should %s %s %s", tc.user,
map[bool]string{true: "be able to", false: "not be able to"}[tc.expected],
tc.action, tc.resource)
}
}
Common Patterns
RESTful API Authorization
// Map HTTP methods to actions
var actionMap = map[string]string{
"GET": "read",
"POST": "create",
"PUT": "update",
"DELETE": "delete",
"PATCH": "update",
}
func RESTfulEnforce(enforcer *casbin.Enforcer, user, resource, method string) (bool, error) {
action, exists := actionMap[method]
if !exists {
return false, fmt.Errorf("unsupported HTTP method: %s", method)
}
return enforcer.Enforce(user, resource, action)
}
Resource Hierarchy
// Model for hierarchical resources
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && r.act == p.act
`
// Policies with wildcards
// p, alice, /api/users/*, read
// p, bob, /api/*, write
This comprehensive guide covers the essential aspects of using Casbin with Go, from basic setup to advanced features and best practices.