Skip to main content

Echo RBAC Implementation

Introduction

Role-Based Access Control (RBAC) is an approach to restricting system access based on the roles of individual users within an organization. In this tutorial, we'll learn how to implement a comprehensive RBAC system in Echo, the high-performance web framework for Go.

RBAC allows you to:

  • Define roles with specific permissions
  • Assign roles to users
  • Control access to resources based on user roles
  • Maintain a clean separation between authorization logic and business logic

This guide takes you through creating a complete RBAC system in Echo from scratch, suitable for both small applications and larger systems that require fine-grained access control.

Prerequisites

Before starting, ensure you have:

  • Basic knowledge of Go programming
  • Familiarity with Echo framework basics
  • Go installed on your system
  • A working Go development environment

Understanding RBAC Concepts

Before diving into code, let's understand the key components of our RBAC system:

  1. Permissions: Individual actions that can be performed (e.g., create:user, delete:post)
  2. Roles: Collections of permissions (e.g., Admin, Editor, User)
  3. Users: Associated with one or more roles
  4. Middleware: Intercepts requests to check if the user has the required permissions

Basic RBAC Implementation

Let's start by creating a simple RBAC implementation for Echo.

Step 1: Define Our Models

First, let's define our core structures:

package rbac

// Permission represents a single action that can be performed
type Permission string

// Role represents a collection of permissions
type Role struct {
Name string
Permissions map[Permission]bool
}

// User represents an authenticated user with roles
type User struct {
ID string
Roles map[string]*Role
}

// RBAC is our role-based access control manager
type RBAC struct {
Roles map[string]*Role
}

// NewRBAC creates a new RBAC instance
func NewRBAC() *RBAC {
return &RBAC{
Roles: make(map[string]*Role),
}
}

Step 2: Implement Core RBAC Functions

Now, let's add the core functions for our RBAC system:

// AddRole adds a new role to the RBAC system
func (r *RBAC) AddRole(name string) *Role {
role := &Role{
Name: name,
Permissions: make(map[Permission]bool),
}
r.Roles[name] = role
return role
}

// AddPermission adds a permission to a role
func (role *Role) AddPermission(permission Permission) {
role.Permissions[permission] = true
}

// HasPermission checks if a role has a specific permission
func (role *Role) HasPermission(permission Permission) bool {
return role.Permissions[permission]
}

// HasPermission checks if a user has a specific permission through any of their roles
func (user *User) HasPermission(permission Permission) bool {
for _, role := range user.Roles {
if role.HasPermission(permission) {
return true
}
}
return false
}

Step 3: Create RBAC Middleware for Echo

Now, let's create Echo middleware to enforce our RBAC rules:

package middleware

import (
"net/http"

"github.com/labstack/echo/v4"
"your-project/rbac"
)

// RBACMiddleware creates middleware that checks if a user has the required permission
func RBACMiddleware(permission rbac.Permission) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Get user from context (after authentication)
user, ok := c.Get("user").(*rbac.User)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}

// Check if user has the required permission
if !user.HasPermission(permission) {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden")
}

return next(c)
}
}
}

Step 4: Setting Up Authentication Context

For the RBAC middleware to work, we need to ensure authenticated users are added to the Echo context:

// AuthMiddleware is a simplified authentication middleware example
// In real applications, use proper authentication like JWT
func AuthMiddleware(rbacManager *rbac.RBAC) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// In a real application, you would validate tokens, etc.
userID := c.Request().Header.Get("X-User-ID")
if userID == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user ID")
}

// Create a user with roles (in real apps, fetch from database)
user := &rbac.User{
ID: userID,
Roles: make(map[string]*rbac.Role),
}

// Add roles to user (in real apps, fetch from database)
if roleNames := c.Request().Header.Get("X-User-Roles"); roleNames != "" {
for _, roleName := range strings.Split(roleNames, ",") {
if role, exists := rbacManager.Roles[roleName]; exists {
user.Roles[roleName] = role
}
}
}

// Set user in context for later middleware
c.Set("user", user)

return next(c)
}
}
}

Complete Example: Blog API with RBAC

Now, let's put everything together in a practical example of a blog API with RBAC protection:

package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"

"your-project/rbac"
customMiddleware "your-project/middleware"
)

func main() {
// Create Echo instance
e := echo.New()

// Create RBAC manager and define roles
rbacManager := rbac.NewRBAC()

// Create roles with permissions
adminRole := rbacManager.AddRole("admin")
adminRole.AddPermission("create:post")
adminRole.AddPermission("update:post")
adminRole.AddPermission("delete:post")
adminRole.AddPermission("read:post")

editorRole := rbacManager.AddRole("editor")
editorRole.AddPermission("create:post")
editorRole.AddPermission("update:post")
editorRole.AddPermission("read:post")

userRole := rbacManager.AddRole("user")
userRole.AddPermission("read:post")

// Add middlewares
e.Use(middleware.Logger())
e.Use(middleware.Recover())

// Apply authentication middleware to all routes
e.Use(customMiddleware.AuthMiddleware(rbacManager))

// Define routes with appropriate permissions
e.GET("/posts", getAllPosts, customMiddleware.RBACMiddleware("read:post"))
e.POST("/posts", createPost, customMiddleware.RBACMiddleware("create:post"))
e.PUT("/posts/:id", updatePost, customMiddleware.RBACMiddleware("update:post"))
e.DELETE("/posts/:id", deletePost, customMiddleware.RBACMiddleware("delete:post"))

// Start server
e.Logger.Fatal(e.Start(":8080"))
}

// Route handlers
func getAllPosts(c echo.Context) error {
// In real app, fetch posts from database
posts := []map[string]interface{}{
{"id": 1, "title": "First Post", "content": "Content 1"},
{"id": 2, "title": "Second Post", "content": "Content 2"},
}
return c.JSON(http.StatusOK, posts)
}

func createPost(c echo.Context) error {
// In real app, save post to database
return c.JSON(http.StatusCreated, map[string]interface{}{
"message": "Post created successfully",
})
}

func updatePost(c echo.Context) error {
id := c.Param("id")
// In real app, update post in database
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "Post " + id + " updated successfully",
})
}

func deletePost(c echo.Context) error {
id := c.Param("id")
// In real app, delete post from database
return c.JSON(http.StatusOK, map[string]interface{}{
"message": "Post " + id + " deleted successfully",
})
}

Testing the RBAC Implementation

You can test this implementation using curl commands:

# User with read-only access
curl -X GET http://localhost:8080/posts \
-H "X-User-ID: user123" \
-H "X-User-Roles: user"

# This will return 200 OK with posts

# User with read-only access trying to create a post
curl -X POST http://localhost:8080/posts \
-H "X-User-ID: user123" \
-H "X-User-Roles: user" \
-H "Content-Type: application/json" \
-d '{"title":"New Post","content":"Content"}'

# This will return 403 Forbidden

# Editor creating a post
curl -X POST http://localhost:8080/posts \
-H "X-User-ID: editor123" \
-H "X-User-Roles: editor" \
-H "Content-Type: application/json" \
-d '{"title":"New Post","content":"Content"}'

# This will return 201 Created

Advanced RBAC Features

1. Resource-Based Permissions

Let's extend our RBAC system to handle resource-based permissions:

// PermissionChecker validates if a user has permission for a specific resource
type PermissionChecker func(c echo.Context, user *User) bool

// ResourceRBACMiddleware creates middleware that checks resource permissions
func ResourceRBACMiddleware(checker PermissionChecker) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Get user from context
user, ok := c.Get("user").(*rbac.User)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "Unauthorized")
}

// Check permission using the provided checker
if !checker(c, user) {
return echo.NewHTTPError(http.StatusForbidden, "Forbidden")
}

return next(c)
}
}
}

// Example usage
e.PUT("/posts/:id", updatePost, ResourceRBACMiddleware(func(c echo.Context, user *User) bool {
postID := c.Param("id")

// Check if user has general permission
if !user.HasPermission("update:post") {
return false
}

// Check if user is the post owner (would need to query database in real app)
// Return true if user is admin or post owner
return user.HasRole("admin") || isPostOwner(postID, user.ID)
}))

2. Role Hierarchy

We can also implement role hierarchy:

// Role with parent role support
type Role struct {
Name string
Permissions map[Permission]bool
Parent *Role
}

// HasPermission checks if a role or any of its parents has a specific permission
func (role *Role) HasPermission(permission Permission) bool {
if role.Permissions[permission] {
return true
}

if role.Parent != nil {
return role.Parent.HasPermission(permission)
}

return false
}

// Usage example:
userRole := rbacManager.AddRole("user")
userRole.AddPermission("read:post")

// Editor inherits from user
editorRole := rbacManager.AddRole("editor")
editorRole.Parent = userRole
editorRole.AddPermission("create:post")
editorRole.AddPermission("update:post")

// Admin inherits from editor
adminRole := rbacManager.AddRole("admin")
adminRole.Parent = editorRole
adminRole.AddPermission("delete:post")

Database Integration

In a production environment, you'll want to store roles and permissions in a database:

// Example using a simple database interface (implementation details omitted)
type RBACRepository interface {
GetRoles() (map[string]*Role, error)
GetUserRoles(userID string) (map[string]*Role, error)
AddRole(role *Role) error
AddPermissionToRole(roleName string, permission Permission) error
}

// Load RBAC data from database
func LoadRBACFromDatabase(repo RBACRepository) (*RBAC, error) {
rbacManager := NewRBAC()

roles, err := repo.GetRoles()
if err != nil {
return nil, err
}

rbacManager.Roles = roles
return rbacManager, nil
}

// Middleware to load user roles from database
func AuthMiddlewareWithDB(rbacManager *RBAC, repo RBACRepository) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Authenticate user (simplified)
userID := c.Request().Header.Get("X-User-ID")
if userID == "" {
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user ID")
}

// Create user and load roles from database
user := &rbac.User{
ID: userID,
}

roles, err := repo.GetUserRoles(userID)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Error loading user roles")
}

user.Roles = roles
c.Set("user", user)

return next(c)
}
}
}

Summary

In this tutorial, we've built a comprehensive RBAC system for Echo web applications:

  1. We created core RBAC models for permissions, roles, and users
  2. We implemented Echo middleware to enforce RBAC policies
  3. We built a complete example of a blog API with different user roles
  4. We extended the system with advanced features like resource-based permissions and role hierarchies
  5. We outlined how to integrate the RBAC system with a database

This RBAC implementation provides a robust foundation for securing your Echo applications with fine-grained access control. You can adapt and extend it based on your specific application requirements.

Further Exercises

  1. Implement caching for role and permission lookups to improve performance
  2. Create a web interface for managing roles and permissions
  3. Add support for time-based permissions that expire after a certain period
  4. Implement attribute-based access control (ABAC) to extend the permission system
  5. Add comprehensive logging for security audits

Additional Resources

💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!