AgentLink uses two independent authentication mechanisms:
- Node auth — JWT tokens issued at enrollment, used for machine-to-machine communication (heartbeat, task claim, result submission)
- Operator auth — JWT session tokens issued at login, used for human control-plane actions (approvals, audit, org management)
POST /operator/login
{
"operator_id": "alice",
"api_key": "<OPERATOR_API_KEY>"
}
Returns a session JWT containing:
sub— operator_idorg_id— the operator's organizationrole— the operator's RBAC role in that orgjti— unique token ID for revocationexp— expiry timestamp
- Login — issues a new token
- Refresh (
POST /operator/refresh) — issues a new token with fresh expiry - Logout (
POST /operator/logout) — adds the token'sjtito the in-memory denylist
| Environment Variable | Default | Description |
|---|---|---|
OPERATOR_API_KEY |
dev-operator-key-change-in-production |
Shared key for operator login |
OPERATOR_TOKEN_EXPIRE_HOURS |
8 |
Session token lifetime |
API_SECRET_KEY |
dev-secret-change-in-production |
HMAC-SHA256 signing key |
| Role | Level | Permissions |
|---|---|---|
owner |
0 | Full control: org management, membership, invites, destructive actions |
admin |
1 | Operational control: approvals, node/task management, invite creation |
operator |
2 | Submit tasks, view nodes/tasks/artifacts, act on approvals |
viewer |
3 | Read-only access to dashboard, tasks, nodes, audit |
Roles are hierarchical — each role includes all permissions of roles below it. An admin can do everything an operator can, plus admin-specific actions.
The require_role(min_role) dependency factory creates FastAPI dependencies that check the operator's role:
@router.post("/admin-action")
async def admin_action(
auth: OperatorContext = Depends(require_role("admin")),
):
# Only admin+ can reach hereEvery resource (node, task, artifact, audit event) belongs to an organization. The org_id is embedded in the operator's JWT and used to scope queries.
On first login, if an operator has no existing membership, they are auto-enrolled in the default organization as an owner. This preserves backward compatibility with single-user deployments.
Operators can belong to multiple organizations. The first membership (by creation date) determines the org/role used in the JWT at login time.
- Dev defaults are insecure —
API_SECRET_KEYandOPERATOR_API_KEYmust be changed in production - Token denylist is in-memory — cleared on API restart; replace with Redis for multi-instance deployments
- No per-user credentials yet — all operators share the same
OPERATOR_API_KEY; planned for a future phase