Authsome

Role-Based Access Control

Hierarchical role and permission management with global and organization-scoped assignments, Warden integration, and RBAC middleware.

Authsome includes a production-grade RBAC system. Roles are organized in a parent-child hierarchy so you can compose complex permission sets from simpler building blocks. Permissions are expressed as action:resource pairs (e.g. read:document, delete:invoice). Users can be assigned roles globally or within a specific organization.

Architecture

RBAC
  ├── Role               (named collection of permissions, optional parent)
  ├── Permission         (action + resource pair)
  ├── GlobalRoleAssignment   (user → role, no org scope)
  └── OrgRoleAssignment      (user → role, scoped to an org)

Permission check: user.globalRoles ∪ user.orgRoles(currentOrg) → match action:resource

The Role model

import "github.com/xraph/authsome/rbac"

type Role struct {
    ID          id.ID         `json:"id"`            // arol_ prefix
    AppID       string        `json:"app_id"`
    Name        string        `json:"name"`          // machine-readable, e.g. "admin"
    DisplayName string        `json:"display_name"`  // human-readable
    Description string        `json:"description"`
    ParentID    *id.ID        `json:"parent_id,omitempty"`
    IsSystem    bool          `json:"is_system"`     // protected system roles
    Permissions []Permission  `json:"permissions,omitempty"`
    CreatedAt   time.Time     `json:"created_at"`
    UpdatedAt   time.Time     `json:"updated_at"`
}
FieldDescription
NameMachine-readable identifier used in code, e.g. "admin", "editor", "viewer"
DisplayNameHuman-readable label shown in UIs
ParentIDWhen set, this role inherits all permissions from the parent role
IsSystemSystem roles (like superadmin) are protected and cannot be deleted
PermissionsThe permissions directly assigned to this role (not including inherited ones)

Hierarchical inheritance

When a role has a ParentID, it inherits all permissions from the parent. Inheritance is deep and recursive -- if the parent has its own parent, those permissions are inherited too.

superadmin
  └── admin (inherits all superadmin permissions)
        └── editor (inherits all admin permissions)
              └── viewer (inherits all editor permissions)

When checking Can(ctx, userID, "delete", "post"), Authsome walks the full role hierarchy and returns true if any role in the chain grants delete:post.

The Permission model

type Permission struct {
    ID          id.ID     `json:"id"`        // aprm_ prefix
    RoleID      id.ID     `json:"role_id"`
    Action      string    `json:"action"`    // e.g. "read", "write", "delete", "manage"
    Resource    string    `json:"resource"`  // e.g. "document", "invoice", "user"
    CreatedAt   time.Time `json:"created_at"`
}

A permission is the combination of Action and Resource. Authsome treats them as strings -- you define your own action and resource vocabulary. Common conventions:

ActionMeaning
readView/list the resource
createCreate new instances
updateModify existing instances
deleteRemove instances
manageFull control (superset of read/create/update/delete)
exportExport or download resource data
publishPublish or make resource public

Creating roles

import "github.com/xraph/authsome/rbac"

// Create a viewer role.
viewerRole, err := auth.RBAC().CreateRole(ctx, &rbac.CreateRoleInput{
    Name:        "viewer",
    DisplayName: "Viewer",
    Description: "Read-only access to documents and reports",
    Permissions: []rbac.PermissionInput{
        {Action: "read", Resource: "document"},
        {Action: "read", Resource: "report"},
    },
})

// Create an editor role that inherits from viewer.
editorRole, err := auth.RBAC().CreateRole(ctx, &rbac.CreateRoleInput{
    Name:        "editor",
    DisplayName: "Editor",
    Description: "Can create and update documents",
    ParentID:    &viewerRole.ID,
    Permissions: []rbac.PermissionInput{
        {Action: "create", Resource: "document"},
        {Action: "update", Resource: "document"},
    },
})

// Create an admin role that inherits from editor.
adminRole, err := auth.RBAC().CreateRole(ctx, &rbac.CreateRoleInput{
    Name:        "admin",
    DisplayName: "Administrator",
    Description: "Full access to all resources",
    ParentID:    &editorRole.ID,
    Permissions: []rbac.PermissionInput{
        {Action: "delete", Resource: "document"},
        {Action: "manage", Resource: "user"},
        {Action: "manage", Resource: "billing"},
    },
})

Adding and removing permissions

// Add a permission to an existing role.
err := auth.RBAC().AddPermission(ctx, roleID, &rbac.PermissionInput{
    Action:   "export",
    Resource: "report",
})

// Remove a permission from a role.
err := auth.RBAC().RemovePermission(ctx, roleID, permissionID)

Assigning roles to users

Global assignment

// Assign a role globally (applies across all orgs).
err := auth.RBAC().AssignRole(ctx, &rbac.AssignRoleInput{
    UserID: userID,
    RoleID: editorRole.ID,
})

// Revoke a global role assignment.
err := auth.RBAC().RevokeRole(ctx, userID, roleID)

Org-scoped assignment

// Assign a role within a specific organization.
err := auth.RBAC().AssignOrgRole(ctx, &rbac.AssignOrgRoleInput{
    UserID: userID,
    OrgID:  orgID,
    RoleID: billingManagerRole.ID,
})

// Revoke the org-scoped assignment.
err := auth.RBAC().RevokeOrgRole(ctx, &rbac.RevokeOrgRoleInput{
    UserID: userID,
    OrgID:  orgID,
    RoleID: billingManagerRole.ID,
})

See Org-Scoped Roles for the full org-scoped RBAC documentation.

Checking permissions

Programmatic check

// Can the user perform "delete" on "document"?
allowed, err := auth.RBAC().Can(ctx, userID, "delete", "document")
if err != nil {
    return err
}
if !allowed {
    return ErrForbidden
}

The Can method automatically includes the org context if authsome.WithOrgID(ctx, orgID) has been called.

Checking multiple permissions

// Check multiple permissions at once -- returns true only if ALL pass.
allowed, err := auth.RBAC().CanAll(ctx, userID, []rbac.PermissionPair{
    {Action: "read",   Resource: "document"},
    {Action: "update", Resource: "document"},
})

// Returns true if ANY of the permissions pass.
allowed, err := auth.RBAC().CanAny(ctx, userID, []rbac.PermissionPair{
    {Action: "manage", Resource: "user"},
    {Action: "update", Resource: "user"},
})

Listing a user's effective permissions

// Get all effective permissions for a user (global + org-scoped, deduplicated).
ctx = authsome.WithOrgID(ctx, orgID)
permissions, err := auth.RBAC().EffectivePermissions(ctx, userID)

for _, p := range permissions {
    fmt.Printf("  %s:%s\n", p.Action, p.Resource)
}

RBAC middleware

Use the built-in middleware to enforce permissions at the HTTP layer:

import "github.com/xraph/authsome/middleware"

// Require the "read:document" permission for GET /documents/*.
router.With(
    middleware.RequirePermission(auth, "read", "document"),
).Get("/documents/{id}", getDocumentHandler)

// Require the "manage:user" permission.
router.With(
    middleware.RequirePermission(auth, "manage", "user"),
).Post("/admin/users", createUserHandler)

The RequirePermission middleware:

  1. Extracts the user ID from the authenticated session (requires the session middleware to run first)
  2. Extracts the org context if the OrgContext middleware has set it
  3. Calls auth.RBAC().Can(ctx, userID, action, resource)
  4. Returns 403 Forbidden with a JSON error body if the check fails

Role-based middleware

// Require that the user has a specific role (by name).
router.With(
    middleware.RequireRole(auth, "admin"),
).Post("/admin/settings", updateSettingsHandler)

Warden integration

Authsome integrates with Warden, the Forge-ecosystem's dedicated RBAC service, for larger deployments where role and permission management needs to be centralized across multiple services.

import (
    "github.com/xraph/authsome"
    "github.com/xraph/authsome/warden"
)

auth := authsome.New(
    authsome.WithWardenRBAC(warden.NewClient(
        warden.WithEndpoint("https://warden.internal:8080"),
        warden.WithAPIKey(os.Getenv("WARDEN_API_KEY")),
    )),
)

When Warden is configured, all auth.RBAC() calls are proxied to the Warden service instead of the local database. This lets you manage roles and permissions in Warden's dashboard and have Authsome enforce them.

HTTP API endpoints

MethodPathDescription
POST/rolesCreate a new role
GET/rolesList all roles
GET/roles/:role_idGet a role by ID
PATCH/roles/:role_idUpdate a role
DELETE/roles/:role_idDelete a role
POST/roles/:role_id/permissionsAdd a permission to a role
DELETE/roles/:role_id/permissions/:perm_idRemove a permission from a role
POST/users/:user_id/rolesAssign a global role to a user
DELETE/users/:user_id/roles/:role_idRevoke a global role from a user
GET/users/:user_id/rolesList a user's global role assignments
GET/users/:user_id/permissionsGet a user's effective permissions
POST/rbac/checkCheck if the authenticated user has a permission

On this page