HelloJohn / docs
Users

Roles & Permissions

Configure role-based access control (RBAC) in HelloJohn — built-in roles, custom roles, permission scopes, and enforcing roles in your backend.

HelloJohn uses role-based access control (RBAC). Each user has one or more roles within a tenant. Roles are included in the JWT so your backend can enforce them without calling HelloJohn.

Built-in roles

Every tenant starts with two built-in roles:

RoleDescription
adminFull access to the tenant. Can manage users, config, orgs, and webhooks.
memberStandard user. No admin access.

Custom roles

Define additional roles per tenant:

POST /v2/admin/tenants/{tenantId}/roles
Authorization: Bearer $ADMIN_TOKEN
Content-Type: application/json

{
  "name": "billing_manager",
  "description": "Can manage billing and subscriptions",
  "permissions": ["billing:read", "billing:write"]
}

Listing roles

GET /v2/admin/tenants/{tenantId}/roles
{
  "roles": [
    { "name": "admin", "built_in": true },
    { "name": "member", "built_in": true },
    { "name": "billing_manager", "built_in": false, "permissions": ["billing:read", "billing:write"] }
  ]
}

Assigning roles

Assign a role when creating or updating a user:

PATCH /v2/admin/tenants/{tenantId}/users/{userId}
{ "roles": ["admin", "billing_manager"] }

Users can have multiple roles. All roles are included in the JWT:

{
  "sub": "usr_01HX...",
  "roles": ["admin", "billing_manager"]
}

Enforcing roles in your backend

Check roles from the verified JWT — no network call needed:

// Go
func requireRole(role string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            token := r.Context().Value("user").(jwt.Token)
            roles, _ := token.Get("roles")
            for _, r := range roles.([]interface{}) {
                if r.(string) == role {
                    next.ServeHTTP(w, r)
                    return
                }
            }
            http.Error(w, "forbidden", http.StatusForbidden)
        })
    }
}

// Usage
mux.Handle("/admin/settings", requireRole("admin")(settingsHandler))
// Node.js / Express
function requireRole(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userRoles: string[] = req.user?.roles ?? []
    const hasRole = roles.some(r => userRoles.includes(r))
    if (!hasRole) return res.status(403).json({ error: 'forbidden' })
    next()
  }
}

// Usage
router.get('/admin/settings', requireAuth, requireRole('admin'), settingsHandler)

Permissions

Permissions are strings attached to roles. They're included in the JWT if configured:

{
  "roles": ["billing_manager"],
  "permissions": ["billing:read", "billing:write"]
}

Enable permissions in JWT claims:

PATCH /v2/admin/tenants/{tenantId}/auth/config
{
  "jwt_claims": {
    "include_permissions": true
  }
}

Permission naming convention

Use resource:action format:

users:read       users:write      users:delete
billing:read     billing:write
reports:read
settings:read    settings:write

Role hierarchy (advanced)

You can configure role inheritance — a higher role inherits all permissions of lower roles:

PATCH /v2/admin/tenants/{tenantId}/roles/admin
{
  "inherits": ["billing_manager", "member"]
}

With this config, admin gets all permissions from billing_manager and member in addition to its own.

MFA requirement by role

Require MFA for specific roles (e.g., admins must use MFA, members don't have to):

PATCH /v2/admin/tenants/{tenantId}/auth/config
{
  "mfa_required_for_roles": ["admin"]
}

Role-based MFA enforcement is evaluated at login time. If a user is promoted to admin while they have an active session, MFA is enforced on their next login.

On this page