Non-Human Entity (NHE) Delegated Tokens¶
Overview¶
NHE Delegation allows authorized users to issue short-lived, tightly scoped access tokens to Non-Human Entities such as LLM agents, automated pipelines, and AI assistants. These tokens let NHEs call protected APIs on behalf of the user, while remaining clearly identifiable as delegated machine tokens with limited privileges and a full audit trail.
Standards¶
This feature builds on established and emerging standards:
- RFC 8693 (OAuth 2.0 Token Exchange) — The
actor_tokenparameter andactJWT claim provide delegation semantics within the existing token exchange grant type. - IETF Agent Authorization Profile (AAP) draft (Feb 2026) — The
agentstructured claim provides agent identity, type, and operator metadata. - DPoP (RFC 9449) — Optionally used for proof-of-possession binding on NHE tokens (when tenant requires it).
Typical Use Case¶
- A researcher is logged in to a web application with an active Authifi session.
- The application's LLM assistant needs to query the organization's research API.
- The application exchanges the user's access token for a 5-minute NHE delegation token scoped to
read:articles search:pubmed. - The LLM agent uses this short-lived token to call the API.
- The API identifies the token as NHE-delegated (via
actclaim) and logs agent activity under the researcher's identity. - After 5 minutes, the token expires. The application requests a fresh token if needed.
Token Exchange Flow¶
Request¶
POST /auth/{tenantId}/oidc/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<user's access token JWT>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
actor_token=agent-researcher-01
actor_token_type=urn:authifi:token-type:nhe-id
resource=https://api.example.com
scope=read:articles search:pubmed
Response¶
{
"access_token": "<JWT>",
"issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
"token_type": "Bearer",
"expires_in": 300
}
Error Responses¶
| Error | Condition |
|---|---|
invalid_grant |
NHE not found, disabled, subject token invalid |
invalid_scope |
Requested scopes exceed delegation policy |
invalid_target |
Resource server not found or rejects NHE tokens |
access_denied |
Feature disabled at tenant/client level, or no delegation policy |
JWT Payload Structure¶
{
"iss": "https://auth.example.com/auth/tenant-id",
"sub": "123",
"aud": "https://api.example.com",
"exp": 1710000900,
"iat": 1710000000,
"jti": "...",
"scope": "read:articles search:pubmed",
"gty": "urn:ietf:params:oauth:grant-type:token-exchange",
"azp": 42,
"org.labshare.tenant.name": "my-tenant",
"org.labshare.tenant.id": 1,
"act": {
"sub": "nhe:agent-researcher-01"
},
"agent": {
"id": "agent-researcher-01",
"type": "llm-autonomous",
"operator": "org:my-tenant"
},
"org.labshare.nhe.delegation": true
}
Key claims:
sub— The delegating user's identity (the human), not the NHE.act.sub— The NHE actor identity, prefixed withnhe:(RFC 8693 delegation semantics).agent— AAP-style agent metadata (id, type, operator, optional model info).org.labshare.nhe.delegation— Boolean marker for resource servers to detect NHE delegation tokens.- No
offline_access— Refresh tokens are never issued for NHE delegation.
Configuration¶
Tenant Settings¶
Enable NHE delegation and configure limits via tenant settings:
{
"settings": {
"nhe": {
"enabled": true,
"defaultMaxTokenLifetime": 300,
"hardMaxTokenLifetime": 900,
"requireDpop": false,
"allowedNheTypes": [
"llm-autonomous",
"llm-assistive",
"automated-pipeline"
],
"maxNhePerTenant": 50,
"auditRetentionDays": 90
}
}
}
| Setting | Default | Description |
|---|---|---|
enabled |
false |
Master feature toggle |
defaultMaxTokenLifetime |
300 |
Default max TTL (seconds) |
hardMaxTokenLifetime |
900 |
Absolute ceiling for any NHE token TTL |
requireDpop |
false |
Require DPoP proof-of-possession |
allowedNheTypes |
[all three] |
Restrict registrable NHE types |
maxNhePerTenant |
50 |
Max NHE registrations per tenant |
auditRetentionDays |
90 |
Audit log retention period |
Client Settings¶
Enable NHE delegation on specific clients:
{
"config": {
"nhe": {
"delegationEnabled": true,
"allowedNheIdentifiers": [],
"maxTokenLifetime": 300
}
}
}
Resource Server Settings¶
Control NHE token acceptance per resource server:
{
"config": {
"nhe": {
"acceptDelegationTokens": true,
"requireDelegationTokens": false,
"allowedNheTypes": ["llm-autonomous"]
}
}
}
TTL Resolution¶
The effective token lifetime is:
min(
tenant.settings.nhe.hardMaxTokenLifetime,
nhe.maxTokenLifetime,
policy.maxTokenLifetime (if set),
client.config.nhe.maxTokenLifetime (if set),
resourceServer.config.accessToken.lifetime
)
Clamped to a minimum of 60 seconds.
Admin UI¶
Tenant administrators manage NHE delegation in the Authifi UI:
- Non-Human Entities (under the tenant SSO menu) — Register agents, set allowed scopes and token lifetime limits, and configure per-user or per-group delegation policies.
- NHE Token Audit (under Monitoring) — Review every delegation token issuance, including user, agent, client, resource server, scopes, TTL, and source IP.
Enable the feature at the tenant level (settings.nhe.enabled), then on each OAuth client and resource server that participates in delegation. Schedule the nhe-audit-retention job under Resources and Tools > Jobs when auditRetentionDays is configured.
Management API¶
NHE Registration¶
| Method | Endpoint | Scope |
|---|---|---|
| POST | /auth/admin/tenants/{tenantId}/non-human-entities |
auth.nhe.create |
| GET | /auth/admin/tenants/{tenantId}/non-human-entities |
auth.nhe.list |
| GET | /auth/admin/tenants/{tenantId}/non-human-entities/{id} |
auth.nhe.list |
| GET | /auth/admin/tenants/{tenantId}/non-human-entities/count |
auth.nhe.list |
| PATCH | /auth/admin/tenants/{tenantId}/non-human-entities/{id} |
auth.nhe.update |
| DELETE | /auth/admin/tenants/{tenantId}/non-human-entities/{id} |
auth.nhe.delete |
Delegation Policies¶
| Method | Endpoint | Scope |
|---|---|---|
| POST | .../non-human-entities/{nheId}/delegation-policies |
auth.nhe.policies.create |
| GET | .../non-human-entities/{nheId}/delegation-policies |
auth.nhe.policies.list |
| GET | .../non-human-entities/{nheId}/delegation-policies/{id} |
auth.nhe.policies.list |
| PATCH | .../non-human-entities/{nheId}/delegation-policies/{id} |
auth.nhe.policies.update |
| DELETE | .../non-human-entities/{nheId}/delegation-policies/{id} |
auth.nhe.policies.delete |
Token Audit¶
| Method | Endpoint | Scope |
|---|---|---|
| GET | .../non-human-entities/token-audit |
auth.nhe.audit.list |
| GET | .../non-human-entities/token-audit/count |
auth.nhe.audit.list |
Resource Server Integration¶
Detecting NHE Tokens¶
NHE delegation tokens are standard JWTs with additional claims. Resource servers can detect them by checking for:
// Using the services-auth utility
import {
getNheDelegationInfo,
isNheDelegationToken,
} from '@axleresearch/services-auth';
// In your route handler:
if (isNheDelegationToken(req.user)) {
const info = getNheDelegationInfo(req.user);
console.log(info.nheIdentifier); // "agent-researcher-01"
console.log(info.agent?.type); // "llm-autonomous"
}
Or directly from the JWT payload:
const isNhe = req.user['org.labshare.nhe.delegation'] === true;
const actorSub = req.user.act?.sub; // "nhe:agent-researcher-01"
Security Considerations¶
- NHE tokens have the same signature and validation as regular access tokens.
- The
subclaim is the human user — authorization decisions based onsubwork normally. - The
actclaim identifies the NHE actor — use this for audit logging and agent-specific restrictions. - NHE tokens are short-lived (typically 5 minutes) and cannot be refreshed.
- Scopes are always a subset of what the delegating user has.
Database Schema¶
Three new tables support NHE delegation:
NonHumanEntity— Registered NHE agents with allowed scopes and TTL limits.NheDelegationPolicy— Per-user or per-group policies controlling who can delegate to which NHEs.NheTokenAudit— Append-only audit log of every NHE token issuance. Rows are written by the token exchange flow and can only be removed by thenhe-audit-retentionjob (see below); all other mutations throw405 Method Not Allowedat the repository level.
Audit Retention¶
Audit rows are pruned by the nhe-audit-retention scheduled job (JOB_TYPE.NHE_AUDIT_RETENTION).
- Runs per tenant and only acts on tenants with
settings.nhe.enabled === trueand a positivesettings.nhe.auditRetentionDays. - Deletes
NheTokenAuditrows whoseissuedAtis older thannow - auditRetentionDaysviaNheTokenAuditRepository.deleteOlderThan, the only sanctioned deletion path. - Batches deletions (default 1000 rows per batch, configurable via
customOptions.batchSize) and caps at 1,000,000 rows per tenant per run; remaining rows are cleaned up on the next run. - Tenants without
auditRetentionDaysset, or with a non-positive value, are skipped so operators can opt in explicitly. - Schedule and enable the job from the admin Jobs dialog like any other scheduled job.
Security Model¶
- Three-level feature gating — Tenant, client, and resource server must all permit NHE delegation.
- Scope intersection — NHE tokens can never exceed the delegating user's permissions. Effective scopes are the intersection of user permissions, NHE allowed scopes, and policy allowed scopes.
- Short lifetimes — Default 5 minutes, hard ceiling 15 minutes, no refresh tokens.
- Full audit trail — Every token issuance is recorded with user, NHE, client, resource server, scopes, TTL, and IP address.
- Delegation, not impersonation — The human identity remains in
sub; the NHE identity is inact. This follows RFC 8693 delegation semantics.