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:
| Role | Description |
|---|---|
admin | Full access to the tenant. Can manage users, config, orgs, and webhooks. |
member | Standard 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:writeRole 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.