Skip to main content

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.