NetOS Authorization and RBAC
NetOS uses Microsoft SSO / Microsoft Entra ID for authentication only. Microsoft proves identity. NetOS decides authorization with internal users, roles, permissions, scopes, and field-level filtering.
Security Model
- Default deny: every
/api/v1route requires an active internal user withapp_accessand the route permission. - Backend enforcement is authoritative. Frontend hiding is only a usability layer.
- Financial and executive fields are filtered before API responses are returned.
- Permission changes are written to the activity audit log with
event_type="authorization". - Sensitive financial access is covered by the normal API activity log and should be expanded for especially sensitive exports or investor reporting endpoints as those are added.
Identity Flow
- Microsoft SSO authenticates the user.
- The reverse proxy forwards trusted identity headers to the API.
- NetOS normalizes the email and optional Entra object ID.
- NetOS loads the internal
usersrecord. - If the user is inactive or has no record, access is denied unless they match the bootstrap Super Admin allowlist.
- Roles and direct user permission overrides are resolved.
- Route and field authorization is applied.
Authenticated does not mean authorized.
Machine Access / API Tokens
Machine users and coding agents should use NetOS API access tokens instead of browser SSO sessions.
API tokens:
- are created by authorized NetOS admins
- are bound to an internal
usersrecord - inherit that user's roles, direct permissions, scopes, and field-level permissions
- are stored only as hashes in
api_access_tokens - are returned in full only once at creation time
- can be revoked without deleting the owning user
- write
last_used_atwhen used - produce principals with
auth_source="api_token"
Supported request headers:
Authorization: Bearer ntos_live_...or:
X-NetOS-API-Key: ntos_live_...Admin endpoints:
GET /api/v1/authz/api-tokens
POST /api/v1/authz/api-tokens
POST /api/v1/authz/api-tokens/{api_token_id}/rotate
DELETE /api/v1/authz/api-tokens/{api_token_id}Creating and revoking tokens requires users_edit; listing tokens requires users_view.
Recommended practice:
- Create a dedicated active internal user for each integration or agent, such as
agent-netos@xiber.net. - Assign the least-privilege role set to that user.
- Prefer expiring tokens.
- Store the full token in the external system's secret manager.
- Rotate tokens periodically and revoke unused tokens.
- Do not create API tokens for human Super Admin users unless there is a short-lived emergency need.
User Lifecycle Management
Admins can now:
- create internal users
- activate and deactivate users
- delete users
- assign and remove scoped roles
- assign direct permission overrides
- generate API tokens for a user
- rotate API tokens, immediately invalidating the previous secret
- revoke API tokens
- audit user and token activity
Deleting a user is a soft delete. NetOS marks the user inactive/deleted and revokes active API tokens for that user. Audit history remains intact.
Rotating a token keeps the same token record but replaces the stored hash and prefix. The old secret stops working immediately, and the new token value is returned once.
Seeing The Current User
The current API-resolved user is available at:
/api/v1/authz/meThe main navigation footer also shows the email that NetOS is authorizing and the source of that identity:
Microsoft SSO: the API received and trusted proxy authentication headers.Dev fallback: the API did not use Microsoft proxy headers and fell back to development auth.
Production should show Microsoft SSO. If it shows Dev fallback, confirm ALLOW_DEV_AUTH=false and TRUST_PROXY_AUTH_HEADERS=true in the running API environment and verify the SSO proxy forwards identity headers such as X-Auth-Request-Email.
Bootstrap Super Admin
Set:
RBAC_BOOTSTRAP_SUPER_ADMIN_EMAILS=admin@example.com,second-admin@example.comWhen one of those Microsoft-authenticated users first reaches the app, NetOS creates or activates the internal user and assigns the Super Admin role. If the variable is not set, NetOS falls back to ADMIN_EMAIL; in dev auth mode it also allows DEV_AUTH_DEFAULT_EMAIL.
Data Model
Core tables:
users: internal user mapped to email and optional Entra object ID.roles: named permission bundles.permissions: granular permission catalog.role_permissions: permissions included in roles.user_roles: role assignments, includingscope_typeandscope_ref_id.user_permissions: optional direct allow/deny overrides.permission_scopes: reusable labels for global, project, site, and department scopes.user_activity_logs: audit history, including authorization changes.
Scopes currently support global, project, site, and department. Global enforcement is live; entity-scope query filters are intended to be applied module by module where project/site assignment data exists.
Default Roles
Super Admin: all permissions.Executive: operational dashboards and financial summaries, including executive financial visibility.Finance Admin: full finance, invoices, reporting, and financial field visibility.Project Manager: project and assigned operational data with project/asset/circuit costs.Network Engineer: technical inventory edit access with relevant asset and circuit costs.Field Technician: assigned operational/site tooling, no financial fields by default.Read-Only User: non-sensitive operational read access only.
Roles are composed of permissions. Do not hard-code role names in new features except for bootstrap or emergency super-admin behavior.
Protecting Routes
Add a permission to apps/api/app/core/permissions.py, then add route mapping in ROUTE_PERMISSION_PREFIXES.
For explicit endpoint checks:
from app.core.auth import require_permission
@router.post("/example", dependencies=[Depends(require_permission("assets_edit"))])
async def create_example(...):
...Every route under /api/v1 also runs require_route_permission.
Protecting Fields
Financial fields must not be sent and hidden only in the browser. Use field permissions:
field.asset_cost.viewfield.circuit_cost.viewfield.customer_revenue.viewfield.margin.viewfield.capex.viewfield.executive_summary.view
Example:
from app.core.auth import can_view_field
if not can_view_field(principal, "field.circuit_cost.view"):
payload["mrc_usd"] = None
payload["nrc_usd"] = NoneExports and search endpoints must use the same filtered payload strategy and must not include protected fields in labels, descriptions, or metadata.
Admin UI
The Admin page includes an Authorization section for:
- creating users
- activating and deactivating users
- assigning roles
- assigning global/project/site/department scopes
- editing role permission composition
The sidebar hides navigation items when the current user lacks the associated view permission.
Setup
Apply migrations:
docker exec netos_api_1 alembic upgrade headRestart API/web after code changes:
docker restart netos_api_1 netos_web_1Run tests:
docker exec netos_api_1 pytest apps/api/tests/test_rbac_authorization.pySecurity Considerations
- Keep Microsoft Conditional Access and MFA in Entra; NetOS remains compatible because it only consumes the authenticated identity.
- Do not grant
app_accessautomatically to every Microsoft user. - Prefer scoped roles for project/site/department work.
- Use direct user permissions sparingly and audit them.
- Never add wildcard permissions.
- New financial, payroll, compensation, debt, investor reporting, export, or search features must define explicit route and field permissions before release.
