---
title: "Authorization Patterns: RBAC and ABAC in Go"
url: "https://vanducng.dev/2024/04/26/Authorization-Patterns-RBAC-ABAC-Go/"
date: "2024-04-26"
series: "go-backend"
categories: ["Backend"]
tags: ["go","security","api"]
---

# Authorization Patterns: RBAC and ABAC in Go

After authentication confirms user identity, authorization determines what they can access. This post covers two powerful authorization models: Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC), with practical Go implementations.

## Authorization Models Overview

```mermaid
flowchart TD
    subgraph Models["Authorization Models"]
        ACL[ACL - Access Control Lists]
        RBAC[RBAC - Role-Based]
        ABAC[ABAC - Attribute-Based]
    end

    ACL --> |User -> Resource| Simple[Simple permissions]
    RBAC --> |User -> Role -> Permission| Medium[Scalable roles]
    ABAC --> |Policies + Attributes| Complex[Dynamic rules]

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff

    class RBAC blueClass
    class ABAC greenClass
```

| Model | Complexity | Use Case |
|-------|------------|----------|
| **ACL** | Low | Simple apps, file systems |
| **RBAC** | Medium | Most applications |
| **ABAC** | High | Complex, dynamic requirements |

## Role-Based Access Control (RBAC)

### Core Concepts

```mermaid
flowchart LR
    U[User] --> R[Role]
    R --> P[Permissions]

    subgraph Roles
        R1[Admin]
        R2[Manager]
        R3[User]
    end

    subgraph Permissions
        P1[Create]
        P2[Read]
        P3[Update]
        P4[Delete]
    end

    R1 --> P1
    R1 --> P2
    R1 --> P3
    R1 --> P4
    R2 --> P2
    R2 --> P3
    R3 --> P2
```

### RBAC Implementation

#### Database Models

```go
// models/rbac.go
type Role struct {
    ID          uint         `json:"id" gorm:"primaryKey"`
    Name        string       `json:"name" gorm:"uniqueIndex;not null"`
    Description string       `json:"description"`
    Permissions []Permission `json:"permissions" gorm:"many2many:role_permissions"`
    CreatedAt   time.Time    `json:"created_at"`
}

type Permission struct {
    ID       uint   `json:"id" gorm:"primaryKey"`
    Name     string `json:"name" gorm:"uniqueIndex;not null"`  // e.g., "users:read"
    Resource string `json:"resource"`                           // e.g., "users"
    Action   string `json:"action"`                             // e.g., "read"
}

type User struct {
    ID       uint   `json:"id" gorm:"primaryKey"`
    Username string `json:"username"`
    Email    string `json:"email"`
    RoleID   uint   `json:"role_id"`
    Role     Role   `json:"role" gorm:"foreignKey:RoleID"`
}
```

#### Permission Service

```go
// services/rbac.go
type RBACService struct {
    db *gorm.DB
}

func NewRBACService(db *gorm.DB) *RBACService {
    return &RBACService{db: db}
}

func (s *RBACService) HasPermission(userID uint, resource, action string) bool {
    var count int64
    permissionName := fmt.Sprintf("%s:%s", resource, action)

    s.db.Model(&User{}).
        Joins("JOIN roles ON roles.id = users.role_id").
        Joins("JOIN role_permissions ON role_permissions.role_id = roles.id").
        Joins("JOIN permissions ON permissions.id = role_permissions.permission_id").
        Where("users.id = ? AND permissions.name = ?", userID, permissionName).
        Count(&count)

    return count > 0
}

func (s *RBACService) GetUserPermissions(userID uint) ([]string, error) {
    var permissions []string

    err := s.db.Model(&Permission{}).
        Joins("JOIN role_permissions ON role_permissions.permission_id = permissions.id").
        Joins("JOIN roles ON roles.id = role_permissions.role_id").
        Joins("JOIN users ON users.role_id = roles.id").
        Where("users.id = ?", userID).
        Pluck("permissions.name", &permissions).Error

    return permissions, err
}

func (s *RBACService) AssignRole(userID, roleID uint) error {
    return s.db.Model(&User{}).Where("id = ?", userID).Update("role_id", roleID).Error
}
```

#### RBAC Middleware

```go
// middleware/rbac.go
func RequirePermission(rbacService *RBACService, resource, action string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := c.GetUint("user_id")

        if !rbacService.HasPermission(userID, resource, action) {
            c.JSON(http.StatusForbidden, gin.H{
                "error": "Permission denied",
                "required": fmt.Sprintf("%s:%s", resource, action),
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

func RequireRole(allowedRoles ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userRole := c.GetString("role")

        for _, role := range allowedRoles {
            if userRole == role {
                c.Next()
                return
            }
        }

        c.JSON(http.StatusForbidden, gin.H{
            "error": "Insufficient role",
        })
        c.Abort()
    }
}
```

#### Using RBAC in Routes

```go
func SetupRoutes(r *gin.Engine, rbacService *RBACService) {
    api := r.Group("/api/v1")
    api.Use(AuthMiddleware())

    // User management - requires specific permissions
    users := api.Group("/users")
    {
        users.GET("", RequirePermission(rbacService, "users", "read"), listUsers)
        users.POST("", RequirePermission(rbacService, "users", "create"), createUser)
        users.PUT("/:id", RequirePermission(rbacService, "users", "update"), updateUser)
        users.DELETE("/:id", RequirePermission(rbacService, "users", "delete"), deleteUser)
    }

    // Admin-only routes
    admin := api.Group("/admin")
    admin.Use(RequireRole("admin"))
    {
        admin.GET("/stats", getStats)
        admin.POST("/roles", createRole)
    }
}
```

### Seeding Roles and Permissions

```go
func SeedRBAC(db *gorm.DB) {
    // Create permissions
    permissions := []Permission{
        {Name: "users:read", Resource: "users", Action: "read"},
        {Name: "users:create", Resource: "users", Action: "create"},
        {Name: "users:update", Resource: "users", Action: "update"},
        {Name: "users:delete", Resource: "users", Action: "delete"},
        {Name: "orders:read", Resource: "orders", Action: "read"},
        {Name: "orders:create", Resource: "orders", Action: "create"},
        {Name: "orders:update", Resource: "orders", Action: "update"},
        {Name: "orders:delete", Resource: "orders", Action: "delete"},
    }

    for _, p := range permissions {
        db.FirstOrCreate(&p, Permission{Name: p.Name})
    }

    // Create roles with permissions
    var allPerms, userPerms, managerPerms []Permission
    db.Find(&allPerms)
    db.Where("name LIKE ?", "%:read%").Or("action = ?", "create").Find(&userPerms)
    db.Where("action IN ?", []string{"read", "create", "update"}).Find(&managerPerms)

    roles := []Role{
        {Name: "admin", Description: "Full access", Permissions: allPerms},
        {Name: "manager", Description: "Manage resources", Permissions: managerPerms},
        {Name: "user", Description: "Basic access", Permissions: userPerms},
    }

    for _, r := range roles {
        var existing Role
        if db.Where("name = ?", r.Name).First(&existing).Error != nil {
            db.Create(&r)
        }
    }
}
```

## Attribute-Based Access Control (ABAC)

ABAC makes decisions based on attributes of the user, resource, action, and environment.

```mermaid
flowchart TD
    subgraph Attributes
        U[User Attributes
Department, Level]
        R[Resource Attributes
Owner, Sensitivity]
        A[Action Attributes
Read, Write]
        E[Environment
Time, Location]
    end

    subgraph Policy["Policy Engine"]
        P[Policy Rules]
    end

    U --> P
    R --> P
    A --> P
    E --> P

    P --> D{Decision}
    D -->|Allow| Allow[Access Granted]
    D -->|Deny| Deny[Access Denied]

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff

    class Policy blueClass
```

### ABAC Implementation

#### Policy Definition

```go
// models/abac.go
type Policy struct {
    ID          uint   `json:"id" gorm:"primaryKey"`
    Name        string `json:"name"`
    Description string `json:"description"`
    Resource    string `json:"resource"`  // e.g., "documents"
    Action      string `json:"action"`    // e.g., "read", "write", "*"
    Conditions  string `json:"conditions" gorm:"type:jsonb"` // JSON conditions
    Effect      string `json:"effect"`    // "allow" or "deny"
    Priority    int    `json:"priority"`  // Higher = evaluated first
}

type PolicyCondition struct {
    Attribute string      `json:"attribute"` // e.g., "user.department"
    Operator  string      `json:"operator"`  // e.g., "equals", "in", "contains"
    Value     interface{} `json:"value"`
}

type AccessRequest struct {
    UserID     uint                   `json:"user_id"`
    Resource   string                 `json:"resource"`
    ResourceID string                 `json:"resource_id"`
    Action     string                 `json:"action"`
    Context    map[string]interface{} `json:"context"`
}
```

#### ABAC Service

```go
// services/abac.go
type ABACService struct {
    db       *gorm.DB
    policies []Policy
}

func NewABACService(db *gorm.DB) *ABACService {
    svc := &ABACService{db: db}
    svc.LoadPolicies()
    return svc
}

func (s *ABACService) LoadPolicies() {
    s.db.Order("priority DESC").Find(&s.policies)
}

func (s *ABACService) Evaluate(req AccessRequest, userAttrs, resourceAttrs map[string]interface{}) bool {
    for _, policy := range s.policies {
        // Check if policy applies to this resource and action
        if policy.Resource != req.Resource && policy.Resource != "*" {
            continue
        }
        if policy.Action != req.Action && policy.Action != "*" {
            continue
        }

        // Evaluate conditions
        if s.evaluateConditions(policy.Conditions, userAttrs, resourceAttrs, req.Context) {
            return policy.Effect == "allow"
        }
    }

    // Default deny
    return false
}

func (s *ABACService) evaluateConditions(conditionsJSON string, user, resource, context map[string]interface{}) bool {
    var conditions []PolicyCondition
    json.Unmarshal([]byte(conditionsJSON), &conditions)

    for _, cond := range conditions {
        value := s.resolveAttribute(cond.Attribute, user, resource, context)
        if !s.evaluateCondition(cond, value) {
            return false
        }
    }
    return true
}

func (s *ABACService) resolveAttribute(attr string, user, resource, context map[string]interface{}) interface{} {
    parts := strings.SplitN(attr, ".", 2)
    if len(parts) != 2 {
        return nil
    }

    var source map[string]interface{}
    switch parts[0] {
    case "user":
        source = user
    case "resource":
        source = resource
    case "context":
        source = context
    default:
        return nil
    }

    return source[parts[1]]
}

func (s *ABACService) evaluateCondition(cond PolicyCondition, value interface{}) bool {
    switch cond.Operator {
    case "equals":
        return value == cond.Value
    case "not_equals":
        return value != cond.Value
    case "in":
        if arr, ok := cond.Value.([]interface{}); ok {
            for _, v := range arr {
                if value == v {
                    return true
                }
            }
        }
        return false
    case "contains":
        if str, ok := value.(string); ok {
            return strings.Contains(str, cond.Value.(string))
        }
        return false
    case "greater_than":
        return toFloat(value) > toFloat(cond.Value)
    case "less_than":
        return toFloat(value) < toFloat(cond.Value)
    default:
        return false
    }
}
```

#### ABAC Middleware

```go
func ABACMiddleware(abacService *ABACService, resource, action string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := c.GetUint("user_id")

        // Get user attributes
        userAttrs := map[string]interface{}{
            "id":         userID,
            "role":       c.GetString("role"),
            "department": c.GetString("department"),
        }

        // Get resource attributes (if accessing specific resource)
        resourceAttrs := map[string]interface{}{}
        if resourceID := c.Param("id"); resourceID != "" {
            resourceAttrs = getResourceAttributes(c, resource, resourceID)
        }

        // Build context
        context := map[string]interface{}{
            "time":      time.Now(),
            "ip":        c.ClientIP(),
            "method":    c.Request.Method,
        }

        req := AccessRequest{
            UserID:   userID,
            Resource: resource,
            Action:   action,
            Context:  context,
        }

        if !abacService.Evaluate(req, userAttrs, resourceAttrs) {
            c.JSON(http.StatusForbidden, gin.H{"error": "Access denied by policy"})
            c.Abort()
            return
        }

        c.Next()
    }
}
```

### Example ABAC Policies

```go
// Policy: Users can only read documents from their department
policy1 := Policy{
    Name:     "department-documents",
    Resource: "documents",
    Action:   "read",
    Effect:   "allow",
    Conditions: `[
        {"attribute": "user.department", "operator": "equals", "value": "resource.department"}
    ]`,
    Priority: 10,
}

// Policy: Managers can edit any document in their department
policy2 := Policy{
    Name:     "manager-edit",
    Resource: "documents",
    Action:   "write",
    Effect:   "allow",
    Conditions: `[
        {"attribute": "user.role", "operator": "equals", "value": "manager"},
        {"attribute": "user.department", "operator": "equals", "value": "resource.department"}
    ]`,
    Priority: 20,
}

// Policy: Deny access outside business hours
policy3 := Policy{
    Name:     "business-hours",
    Resource: "*",
    Action:   "*",
    Effect:   "deny",
    Conditions: `[
        {"attribute": "context.hour", "operator": "less_than", "value": 9}
    ]`,
    Priority: 100,
}
```

## Combining RBAC and ABAC

```go
func CombinedAuthMiddleware(rbac *RBACService, abac *ABACService, resource, action string) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := c.GetUint("user_id")

        // First check: RBAC (does user's role have this permission?)
        if !rbac.HasPermission(userID, resource, action) {
            c.JSON(http.StatusForbidden, gin.H{"error": "Permission denied"})
            c.Abort()
            return
        }

        // Second check: ABAC (do attributes satisfy policies?)
        userAttrs := getUserAttributes(c)
        resourceAttrs := getResourceAttributes(c, resource, c.Param("id"))

        req := AccessRequest{
            UserID:   userID,
            Resource: resource,
            Action:   action,
        }

        if !abac.Evaluate(req, userAttrs, resourceAttrs) {
            c.JSON(http.StatusForbidden, gin.H{"error": "Policy denied access"})
            c.Abort()
            return
        }

        c.Next()
    }
}
```

## Resource Ownership Check

```go
func ResourceOwnerMiddleware(resource string, getOwnerID func(c *gin.Context) uint) gin.HandlerFunc {
    return func(c *gin.Context) {
        userID := c.GetUint("user_id")
        userRole := c.GetString("role")

        // Admins bypass ownership check
        if userRole == "admin" {
            c.Next()
            return
        }

        ownerID := getOwnerID(c)
        if ownerID != userID {
            c.JSON(http.StatusForbidden, gin.H{"error": "Not owner of this resource"})
            c.Abort()
            return
        }

        c.Next()
    }
}

// Usage
router.PUT("/orders/:id",
    AuthMiddleware(),
    ResourceOwnerMiddleware("orders", func(c *gin.Context) uint {
        var order Order
        db.First(&order, c.Param("id"))
        return order.UserID
    }),
    updateOrder,
)
```

## Summary

| Model | Best For | Implementation |
|-------|----------|----------------|
| **RBAC** | Most applications | Roles + Permissions tables |
| **ABAC** | Complex, dynamic rules | Policy engine + attributes |
| **Combined** | Enterprise apps | RBAC base + ABAC overrides |

```mermaid
flowchart TD
    R[Request] --> Auth[Authentication]
    Auth --> RBAC{RBAC Check}
    RBAC -->|Has Role| ABAC{ABAC Check}
    RBAC -->|No Role| D1[Deny]
    ABAC -->|Policy Allows| Allow[Allow]
    ABAC -->|Policy Denies| D2[Deny]

    classDef blueClass fill:#4A90E2,stroke:#333,stroke-width:2px,color:#fff
    classDef greenClass fill:#27AE60,stroke:#333,stroke-width:2px,color:#fff

    class RBAC blueClass
    class ABAC greenClass
```

---

*Next post: [Securing Go APIs: OWASP Best Practices](https://vanducng.dev/2024/05/03/Securing-Go-APIs-OWASP-Best-Practices/) - Protecting your API against common security vulnerabilities.*
