Caskin is a multi-domain RBAC (Role-Based Access Control) authorization library for Go, built on top of casbin.
It focuses on managing authorization business — create and manage users, roles, objects, and domains with fine-grained permission control across multiple tenants.
- Multi-domain / multi-tenant — isolated RBAC per domain
- Role hierarchy — role inheritance within a domain
- Dictionary-driven — define objects, roles, and policies via TOML config
- Backend & frontend permissions — separate API-level and UI-level permission packages
- Pluggable storage — supports SQLite, MySQL, PostgreSQL, SQL Server
- Optional Redis watcher — sync enforcer across multiple instances
go get github.com/awatercolorpen/caskinCreate caskin.toml to declare your features, backend APIs, frontend menus, and initial roles/policies:
# Features your system exposes
feature = [
{name = "article"},
]
# Backend API endpoints
backend = [
{path = "api/article", method = "GET"},
{path = "api/article", method = "POST"},
]
# Frontend menu/UI items
frontend = [
{name = "article", type = "menu"},
]
# Packages bundle backend + frontend into a logical permission unit
package = [
{key = "article", backend = [["api/article", "GET"], ["api/article", "POST"]], frontend = [["article", "menu"]]},
]
# Initial objects created when a domain is reset
creator_object = [
{name = "role_root", type = "role"},
]
# Initial roles created when a domain is reset
creator_role = [
{name = "admin"},
{name = "member"},
]
# Initial policies assigned to those roles
creator_policy = [
{role = "admin", object = "role_root", action = ["read", "write", "manage"]},
{role = "admin", object = "github.com/awatercolorpen/caskin::article", action = ["read"]},
{role = "member", object = "role_root", action = ["read"]},
]Caskin requires four types: User, Role, Object, and Domain. You can embed them in your own models, or use the ready-made implementations in the example package as a starting point.
import "github.com/awatercolorpen/caskin/example"
// example.User implements caskin.User
// example.Role implements caskin.Role
// example.Object implements caskin.Object
// example.Domain implements caskin.DomainEach type must satisfy its interface. For example, caskin.User requires GetID(), SetID(), Encode(), and Decode().
package main
import (
"log"
"github.com/awatercolorpen/caskin"
"github.com/awatercolorpen/caskin/example"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
// 1. Register your concrete types (generics-based, called once at startup)
caskin.Register[*example.User, *example.Role, *example.Object, *example.Domain]()
// 2. Configure storage (SQLite for local dev; swap for MySQL/Postgres in production)
dbOption := &caskin.DBOption{
DSN: "./caskin.db",
Type: "sqlite",
}
// 3. Auto-migrate your tables
db, err := dbOption.NewDB()
if err != nil {
log.Fatal(err)
}
db.AutoMigrate(&example.User{}, &example.Role{}, &example.Object{}, &example.Domain{})
// 4. Build the service
service, err := caskin.New(&caskin.Options{
Dictionary: &caskin.DictionaryOption{Dsn: "caskin.toml"},
DB: dbOption,
})
if err != nil {
log.Fatal(err)
}
// 5. Bootstrap: create a domain and a superadmin
domain := &example.Domain{Name: "my-org"}
superadmin := &example.User{Email: "admin@example.com"}
_ = service.CreateDomain(domain)
_ = service.ResetDomain(domain) // creates initial objects + roles from caskin.toml
_ = service.ResetFeature(domain) // registers features + backend/frontend definitions
_ = service.CreateUser(superadmin)
_ = service.AddSuperadmin(superadmin)
// 6. Permission check
roles, _ := service.GetRole(superadmin, domain)
log.Printf("domain %q has %d roles", domain.Name, len(roles))
}Use the operator (superadmin) and the target domain to call any service method:
// Create a regular user
user := &example.User{Email: "alice@example.com"}
service.CreateUser(user)
// Assign a role to the user
roles, _ := service.GetRole(superadmin, domain)
adminRole := roles[0] // first role is "admin" by default
service.ModifyUserRolePerRole(superadmin, domain, adminRole, []*caskin.UserRolePair{
{User: user, Role: adminRole},
})
// Check what roles the user has
pairs, _ := service.GetUserRole(superadmin, domain)
for _, p := range pairs {
log.Printf("user %v → role %v", p.User, p.Role)
}
// Use a scoped CurrentService for a specific operator+domain context
current := service.SetCurrent(user, domain)
myRoles, _ := current.GetCurrentRole()
myObjects, _ := current.GetCurrentObject()The playground package sets up a fully initialized in-memory environment for quick testing:
import "github.com/awatercolorpen/caskin/playground"
stage, err := playground.NewPlaygroundWithSqlitePath(t.TempDir())
// stage.Service — the caskin service
// stage.Superadmin, stage.Admin, stage.Member — pre-created users
// stage.Domain — pre-created domain with roles and policies| Doc | Description |
|---|---|
| Configuration | All config options: DB, dictionary, watcher |
| Design | Architecture and design decisions |
| API | Full service method reference |
| Driver | DBOption.Type | DSN example |
|---|---|---|
| SQLite | "sqlite" |
./caskin.db |
| MySQL | "mysql" |
user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True |
| PostgreSQL | "postgres" |
host=localhost user=postgres password=pass dbname=caskin port=5432 |
service, err := caskin.New(&caskin.Options{
Dictionary: &caskin.DictionaryOption{Dsn: "caskin.toml"},
DB: dbOption,
Watcher: &caskin.WatcherOption{
Type: "redis",
Address: "localhost:6379",
Password: "",
Channel: "/caskin",
},
})